Railway Operation Simulator  v2.23.0
A railway simulator for Windows
TrainUnit.cpp
Go to the documentation of this file.
1 // TrainUnit.cpp
2 /*
3  BEWARE OF COMMENTS in .cpp files: they were accurate when written but have
4  sometimes been overtaken by changes and not updated
5  Comments in .h files are believed to be accurate and up to date
6 
7  This is a source code file for "railway.exe", a railway operation
8  simulator, written originally in Borland C++ Builder 4 Professional with
9  later updates in Embarcadero C++Builder.
10  Copyright (C) 2010 Albert Ball [original development]
11 
12  This program is free software: you can redistribute it and/or modify
13  it under the terms of the GNU General Public License as published by
14  the Free Software Foundation, either version 3 of the License, or
15  (at your option) any later version.
16 
17  This program is distributed in the hope that it will be useful,
18  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  GNU General Public License for more details.
21 
22  You should have received a copy of the GNU General Public License
23  along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25 // ---------------------------------------------------------------------------
26 #include <Classes.hpp>
27 #include <Controls.hpp>
28 #include <StdCtrls.hpp>
29 #include <Forms.hpp>
30 #include <Buttons.hpp>
31 #include <ExtCtrls.hpp>
32 #include <Menus.hpp>
33 #include <Dialogs.hpp>
34 #include <Graphics.hpp>
35 #include <ComCtrls.hpp>
36 #include <fstream>
37 #include <vector>
38 #include <algorithm> //for sort
39 #include <vcl.h>
40 #include <stdlib.h> //for rand() & random()
41 #include <math.hpp> //for speed & performance calcs
42 
43 #pragma hdrstop
44 
45 #include "TrainUnit.h"
46 #include "TrackUnit.h"
47 #include "TextUnit.h" //for displaying train service reference
48 #include "GraphicUnit.h"
49 //#include "DisplayUnit.h" included in TrackUnit.h
50 #include "PerfLogUnit.h"
51 #include "Utilities.h"
52 
53 // ---------------------------------------------------------------------------
54 #pragma package(smart_init)
55 
57 
58 // ---------------------------------------------------------------------------
59 
60 int TTrain::NextTrainID = 0; // has to be initialised outside the class
61 
62 // ---------------------------------------------------------------------------
63 
64 TExitInfo::TExitInfo() //default constructor
65 {
66  ServiceReference = " ";
67  RepeatNumber = 0;
68  TimeToExitSecs = -1;
69 }
70 
71 // ---------------------------------------------------------------------------
72 
73 TTrain::TTrain(int Caller, int RearStartElementIn, int RearStartExitPosIn, AnsiString InputCode, int StartSpeedIn, int MassIn, double MaxRunningSpeedIn,
74  double MaxBrakeRateIn, double PowerAtRailIn, TTrainMode TrainModeIn, TTrainDataEntry *TrainDataEntryPtrIn, int RepeatNumberIn, int IncrementalMinutesIn,
75  int IncrementalDigitsIn, int SignallerMaxSpeedIn) : RearStartElement(RearStartElementIn), RearStartExitPos(RearStartExitPosIn), HeadCode(InputCode),
76  StartSpeed(StartSpeedIn), Mass(MassIn), MaxRunningSpeed(MaxRunningSpeedIn), MaxBrakeRate(MaxBrakeRateIn), PowerAtRail(PowerAtRailIn),
77  TrainMode(TrainModeIn), TrainDataEntryPtr(TrainDataEntryPtrIn), RepeatNumber(RepeatNumberIn), IncrementalMinutes(IncrementalMinutesIn),
78  IncrementalDigits(IncrementalDigitsIn), SignallerMaxSpeed(SignallerMaxSpeedIn)
79 /*
80  Construct a new train with general default values and input values for position and headcode.
81  Create the frontcode, headcode and background graphics here but don't delete them in a destructor.
82  This is because trains are kept in a vector and vectors erase elements during internal operations.
83  Deletion is explicit by using a special function. Increment the static class member NextTrainID
84  after setting this train's ID.
85 */
86 
87 {
88  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TTrain," + AnsiString(RearStartElementIn) + "," +
89  AnsiString(RearStartExitPosIn) + "," + AnsiString(InputCode) + "," + AnsiString(StartSpeedIn) + "," + AnsiString(MassIn) + "," +
90  AnsiString(TrainModeIn));
91  // AutoControl = true;//all trains start in auto control
92  UpdateCounter = 0;
93  TimeTimeLocArrived = false;
94  Derailed = false;
95  DerailPending = false;
96  Crashed = false;
97  StoppedAtBuffers = false;
98  StoppedAtSignal = false;
99  StoppedAtLocation = false;
100  StoppedAfterSPAD = false;
101  StoppedWithoutPower = false; // new at v2.4.0
102  StoppedForTrainInFront = false;
103  TrainInFront = false; //new at v2.18.0
104  SignallerStoppingFlag = false;
105  SignallerStopped = false;
106  SignallerRemoved = false;
107  NotInService = false;
108  HoldAtLocationInTTMode = false;
109  AllowedToPassRedSignal = false;
110  CallingOnFlag = false;
111  BeingCalledOn = false;
112  DepartureTimeSet = false;
114  TimetableFinished = false;
115  LastActionDelayFlag = false;
116  OneLengthAccelDecel = false;
117  TrainCrashedInto = -1;
119  Plotted = false;
120  TrainGone = false;
121  SPADFlag = false;
122  FrontCodePtr = new Graphics::TBitmap;
123  FrontCodePtr->PixelFormat = pf8bit;
124  FrontCodePtr->Height = 8;
125  FrontCodePtr->Width = 8;
127  FrontCodePtr->Transparent = false;
128  AValue = sqrt(2 * PowerAtRail / Mass);
130  TerminatedMessageSent = false;
131  JoinedOtherTrainFlag = false;
133  FollowOnServiceRef = ""; //added at v2.12.0
134  TreatPassAsTimeLocDeparture = false; //added at v2.12.0
135  StepForwardFlag = false;
137  for(int x = 0; x < 4; x++)
138  {
139  HeadCodeGrPtr[x] = new Graphics::TBitmap;
140  HeadCodeGrPtr[x]->PixelFormat = pf8bit;
141  HeadCodeGrPtr[x]->Height = 8;
142  HeadCodeGrPtr[x]->Width = 8;
144  HeadCodeGrPtr[x]->Transparent = false;
145  }
146  for(int x = 0; x < 4; x++)
147  {
148  BackgroundPtr[x] = new Graphics::TBitmap;
149  BackgroundPtr[x]->PixelFormat = pf8bit;
150  BackgroundPtr[x]->Height = 8;
151  BackgroundPtr[x]->Width = 8;
153  BackgroundPtr[x]->Transparent = false;
154  }
155  for(int x = 0; x < 4; x++)
156  {
158  // set here to ensure have values
159  }
160  for(int x = 0; x < 4; x++)
161  {
162  PlotElement[x] = -1; // marker for not plotted yet
163  }
164  for(int x = 0; x < 3; x++)
165  {
166  OldZoomOutElement[x] = -1; // marker for not plotted yet
167  }
169  NextTrainID++;
170 
171  // new values added to complete initialisation of all TTrain variables
172 
173  // ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0)); can't be initialised yet as session trains created with Null
174  // TrainDataEntryPtr, initialise in AddTrain
176  FrontElementLength = 0;
177  EntrySpeed = 0;
178  ExitSpeedHalf = 0;
179  ExitSpeedFull = 0;
180  MaxExitSpeed = 0;
181  BrakeRate = 0;
182  CoastingBrakeRate = 0.03; //added at v2.18.0
184  FirstHalfMove = true;
185  EntryTime = 0;
186  ExitTimeHalf = 0;
187  ExitTimeFull = 0;
188  ReleaseTime = 0;
189  TRSTime = 0;
190  LastActionTime = 0;
191  Straddle = MidLag;
192  LeadElement = -1;
193  LeadEntryPos = 0;
194  LeadExitPos = 0;
195  MidElement = -1;
196  MidEntryPos = 0;
197  MidExitPos = 0;
198  LagElement = -1;
199  LagEntryPos = 0;
200  LagExitPos = 0;
201  TrainFailed = false; // added at v2.4.0
202  for(int x = 0; x < 4; x++)
203  {
204  HOffset[x] = 0;
205  VOffset[x] = 0;
206  PlotEntryPos[x] = 0;
207  }
208  OpTimeToAct = 60; // default value, new at v2.2.0
209  TimeToExit = -1;
210  ExitPair.first = -1;
211  ExitPair.second = -1;
212  MinsDelayed = 0.0; // new at v2.2.0
213  FirstLaterStopRecoverableTime = 0.0; // new at v2.2.0
214  FinishJoinLogSent = false;
215  // added at v2.4.0 to prevent repeatdly logging the event
218  // added at v2.4.0, no need to include in session file as will only be sent once & better that way
222  ZeroPowerNoCDTMessage = false;
227  ZeroPowerDepartMessage = false;
228  TrainInFrontMessage = false;
229  TrainFailurePending = false;
230  SkippedDeparture = false;
231  ActionsSkippedFlag = false;
232  SkipPtrValue = 0;
233  TrainSkippedEvents = 0;
234  DelayedRandMins = 0; //added at v2.13.0
235  NewDelay = 0; //added at v2.13.0
236  CumulativeDelayedRandMinsOneTrain = 0; //added at v2.13.0
237  ActualArrivalTime = TDateTime(0); //added at v2.13.0
238  LastSigPassedFailed = false; //added at v2.13.0
239  NonDefaultMinDwellTimeFlag = false; //added at v2.23.0
240  ArrivalMinDwellTime = 30.0; //added at v2.23.0, default value
241  LongServRefEnteredFlag = false;
242  LongServRefNameBitmap = new Graphics::TBitmap; //added at v2.22.0 these are for displaying long serv refs above the train
243  LongServRefNameBitmap->PixelFormat = pf8bit;
244  LongServRefNameBitmap->Height = 13; //needs to be 13 so text can be entered, though top of text is 3 pixels below the top
245  LongServRefNameBitmap->Width = 54;
247 
248  LongServRefWorkingBitmap = new Graphics::TBitmap; //added at v2.22.0 these are for displaying long serv refs above the train
249  LongServRefWorkingBitmap->PixelFormat = pf8bit;
250  LongServRefWorkingBitmap->Height = 10; //shorter than name bitmap as top 3 pixels not occupied by text
251  LongServRefWorkingBitmap->Width = 54;
253 
254  ImageLongServRefBitmap = new Graphics::TBitmap; //added at v2.22.0 these are for displaying long serv refs on the operating image
255  ImageLongServRefBitmap->PixelFormat = pf8bit;
256  ImageLongServRefBitmap->Height = 10; //shorter than name bitmap as top 3 pixels not occupied by text
257  ImageLongServRefBitmap->Width = 54;
259  Utilities->CallLogPop(648);
260 }
261 
262 // ---------------------------------------------------------------------------
263 
264 void TTrain::DeleteTrain(int Caller)
265 /*
266  Delete train heap objects (bitmaps) explicitly by this special function rather than by a destructor, because vectors
267  erase elements during internal operations & if TTrain had an explicit destructor that deleted the heap elements then
268  it would be called when a vector element was erased. Calling the default TTrain destructor doesn't matter because all that
269  does is release the memory of the members (including pointers to the bitmaps), it doesn't destroy the bitmaps themselves.
270  It's important therefore to call this function before erasing the vector element, otherwise the pointers to the bitmaps
271  would be lost and the bitmaps never destroyed, thereby causing memory leaks.
272  No need to delete HeadCodePosition as that just points to existing bitmaps
273 */{
274  // if(NoDelete) return;//used when a TTrain is created to hold copied values from elsewhere
275  TrainController->LogEvent("" + AnsiString(Caller) + ",DeleteTrain," + HeadCode);
276  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",DeleteTrain," + HeadCode);
277  if(Display->ZoomOutFlag)
278  {
280  }
281  if(FrontCodePtr == 0)
282  {
283  throw Exception("Error in attempting to delete FrontCodePtr");
284  }
285  delete FrontCodePtr;
286  FrontCodePtr = 0;
287  for(int x = 0; x < 4; x++)
288  {
289  if(BackgroundPtr[x] == 0)
290  {
291  throw Exception("Error in attempting to delete BackgroundPtr[" + AnsiString(x) + "]");
292  }
293  delete BackgroundPtr[x];
294  BackgroundPtr[x] = 0;
295  }
296  for(int x = 0; x < 4; x++)
297  {
298  if(HeadCodeGrPtr[x] == 0)
299  {
300  throw Exception("Error in attempting to delete HeadCodeGrPtr[" + AnsiString(x) + "]");
301  }
302  delete HeadCodeGrPtr[x];
303  HeadCodeGrPtr[x] = 0;
304  }
305  delete LongServRefNameBitmap;
307  delete ImageLongServRefBitmap;
311  Utilities->CallLogPop(649);
312 }
313 
314 // ---------------------------------------------------------------------------
315 
317 /*
318  Plots the train starting position on screen. Note that the check for starting on straight points &
319  on wrongly set points is carried out in TrainControllerUnit [but have to allow for starting on points because
320  ChangeDirection calls this function]. Train starts on Lead & Mid elements & Straddle = LeadMid unless
321  entering at a continuation in which case Straddle = MidLag & train not plotted immediately.
322  Set the headcode graphics pointers from the headcode text, then check whether starting at a
323  continuation. If so set Mid & Lag elements to -1 so they won't be plotted, and set Lead values
324  for the continuation element. Otherwise set Lead and Mid values,
325 
326  and Lead element value unless
327  Mid element is a buffer or continuation. Set Straddle, then for the Mid element set the graphic
328  offsets and headcode positions and front code. Pick up background bitmaps for the Mid element,
329  then check if a train on either Mid or Lag and if so give a warning message and return false so
330  that the calling function can delete the train. Plot the Mid element train values then do similarly
331  for the Lag element - set offsets, pick up background bitmaps, and plot the rear two segments of
332  the train. Finally set the Plotted flag and return true.
333 */{
334  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotStartPosition," + HeadCode);
335  int NextElementPosition, NextEntryPos, ElementLength, SpeedLimit;
336 
338  // PlotStartTime = TrainController->TTClockTime;
339  FirstHalfMove = true;
340 
341  // if enter at continuation then don't plot anything at start, but set TrainIDOnElement for continuation entry so as to
342  // 'claim' it for this train to prevent any other waiting trains trying to enter
344  {
345  LagElement = -1; // not to be plotted
346  LagExitPos = 0; // not to be plotted
347  LagEntryPos = 0; // not to be plotted
348  MidElement = -1; // not to be plotted
349  MidExitPos = 0; // not to be plotted
350  MidEntryPos = 0; // not to be plotted
352  LeadExitPos = 1; // will be 1 for continuation entry
353  LeadEntryPos = 0;
354 
356  MaxExitSpeed = StartSpeed; // initial value
358  ElementLength = Track->TrackElementAt(164, LeadElement).Length01;
359  SpeedLimit = Track->TrackElementAt(165, LeadElement).SpeedLimit01;
360  if(EntrySpeed > SpeedLimit)
361  {
362  EntrySpeed = SpeedLimit;
363  }
365  {
367  }
369  // LeadElement is the element to be entered
370 
371  // Precautionary check - If need to brake EntrySpeed may be too high, so set it to the speed at which
372  // can achieve ExitSpeedFull at the half braking rate.
374  {
375  double TempEntrySpeed = sqrt((MaxExitSpeed * MaxExitSpeed) + (3.6 * 3.6 * MaxBrakeRate * ElementLength)); // half braking
376  if(TempEntrySpeed < EntrySpeed)
377  {
378  EntrySpeed = TempEntrySpeed;
380  }
381  }
382  Straddle = MidLag; // only for starting on a continuation
384  // no need to stop gap flashing if start on continuation
385  }
386  else // not starting at a continuation
387  {
388  LagElement = -1;
389  LagEntryPos = 0;
390  LagExitPos = 0;
397 
399  MaxExitSpeed = StartSpeed; // initial value
401  bool TempDerail = false; // dummy
402  NextElementPosition = Track->TrackElementAt(168, LeadElement).Conn[Track->GetAnyElementOppositeLinkPos(2, LeadElement, LeadEntryPos, TempDerail)];
404  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
405  {
406  StoppedWithoutPower = true;
407  }
408  // facing buffers check - ignore starting speed if start facing buffers
409  StoppedAtBuffers = false;
410  // need to set here as well as in UpdateTrain() in case paused during signaller change direction
413  {
414  FrontElementSpeedLimit = Track->TrackElementAt(494, LeadElement).SpeedLimit01; // use 01 for convenience, not used
415  FrontElementLength = Track->TrackElementAt(495, LeadElement).Length01; // use 01 for convenience, not used
416  EntrySpeed = 0;
417  ExitSpeedHalf = 0;
418  ExitSpeedFull = 0;
419  MaxExitSpeed = 0;
420  // SetTrainMovementValues not called so set this here
421  BrakeRate = 0;
424  StoppedAtSignal = false;
425  // new v2.2.0: can't be at buffers and signal! If was set then won't be reset as later
426  // signal check is an 'else'
427  if(!StoppedAtLocation)
428  {
429  StoppedAtBuffers = true; // stopped at location takes precedence
430  }
431  }
432 
433  // facing continuation check - don't allow to stop even if no power
435  {
436  FrontElementSpeedLimit = Track->TrackElementAt(509, LeadElement).SpeedLimit01; // use 01 for convenience, not used
437  FrontElementLength = Track->TrackElementAt(510, LeadElement).Length01; // use 01 for convenience, not used
441  BrakeRate = 0;
442  ExitTimeHalf = TrainController->TTClockTime + TDateTime(1.8 * (double) FrontElementLength / EntrySpeed / 86400);
443  ExitTimeFull = TrainController->TTClockTime + TDateTime(3.6 * (double) FrontElementLength / EntrySpeed / 86400);
444  }
445 
446  // Signal check
447  else if((NextElementPosition > -1) && (NextEntryPos > -1))
448  // condition check added as precaution after SloughIECC error reported by James U
449  {
450  if((Track->TrackElementAt(170, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
451  (Track->TrackElementAt(171, NextElementPosition).Attribute == 0) && !StoppedWithoutPower)
452  {
453  FrontElementSpeedLimit = Track->TrackElementAt(172, LeadElement).SpeedLimit01; // use 01 for convenience, not used
454  FrontElementLength = Track->TrackElementAt(173, LeadElement).Length01; // use 01 for convenience, not used
455  EntrySpeed = 0;
456  ExitSpeedHalf = 0;
457  ExitSpeedFull = 0;
458  MaxExitSpeed = 0;
459  BrakeRate = 0;
462  if(!StoppedAtLocation) //if it is stopped at location then don't want StoppedAtSignal until departure time if still red then, & UpdateTrain takes care of thet
463  {
464  StoppedAtSignal = true;
466  // TrainController->LogActionError(39, HeadCode, "", SignalHold, Track->TrackElementAt(754, NextElementPosition).ElementID);
467  }
469  {
470  // set both StoppedAtLocation & StoppedAtSignal, so that 'pass stop signal' is offered in popup menu rather than move
471  // forwards, but don't change the background colour so still shows as stopped at location
472  StoppedAtSignal = true;
473  }
474  }
475  else
476  {
477  StoppedAtSignal = false;
478  if(NextEntryPos > 1)
479  {
480  ElementLength = Track->TrackElementAt(174, NextElementPosition).Length23;
481  SpeedLimit = Track->TrackElementAt(175, NextElementPosition).SpeedLimit23;
482  }
483  else
484  {
485  ElementLength = Track->TrackElementAt(176, NextElementPosition).Length01;
486  SpeedLimit = Track->TrackElementAt(177, NextElementPosition).SpeedLimit01;
487  }
488  if(EntrySpeed > SpeedLimit)
489  {
490  EntrySpeed = SpeedLimit;
491  }
493  {
495  }
497  TDateTime TestTime = TrainController->TTClockTime; // test
498  AnsiString TimeString = Utilities->Format96HHMMSS(TestTime); // test
499  SetTrainMovementValues(2, NextElementPosition, NextEntryPos);
500  // NextElement is the element to be entered
501 
502  // Precautionary check - If need to brake EntrySpeed may be too high, so set it to the speed at which
503  // can achieve ExitSpeedFull at the half braking rate.
505  {
506  double TempEntrySpeed = sqrt((MaxExitSpeed * MaxExitSpeed) + (3.6 * 3.6 * MaxBrakeRate * ElementLength));
507  // half braking
508  if(TempEntrySpeed < EntrySpeed)
509  {
510  EntrySpeed = TempEntrySpeed;
511  SetTrainMovementValues(3, NextElementPosition, NextEntryPos);
512  }
513  }
514  }
515  }
517  {
518  throw Exception("Error, LeadElement Exit Connection is NotSet");
519  }
520  }
521  if(MidElement > -1) // will be -1 if start on continuation
522  {
523  Straddle = LeadMid;
527  {
528  for(int x = 0; x < 4; x++)
529  {
530  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
531  }
532  }
533  else
534  {
535  for(int x = 0; x < 4; x++)
536  {
538  }
539  }
540  if(TrainMode == Timetable)
541  {
543  }
544  else
545  {
547  }
549  // pick up background bitmaps [0] & [1] & plot HeadCodes [0] & [1]
550 
553 /* Move check to AddTrain, also, now that can start on bridges need to check that other train is on same track before refusing
554  if((Track->TrackElementAt(182, LeadElement).TrainIDOnElement > -1) || ((MidElement > -1) && (Track->TrackElementAt(183, MidElement).TrainIDOnElement > -1)))
555  {
556  ShowMessage("Can't place train " + HeadCode + "; another train already present at location");
557  Utilities->CallLogPop(651);
558  return false;
559  }
560 */
565  PlotTrainGraphic(8, 0, Display);
566  PlotTrainGraphic(9, 1, Display);
567 
570 
571  // pick up background bitmaps [2] & [3]
572 
575 
576  PlotElement[2] = MidElement;
578  PlotElement[3] = MidElement;
580  PlotTrainGraphic(10, 2, Display);
581  PlotTrainGraphic(11, 3, Display);
582  // Plotted = true; set in PlotTrainGraphic
583  //below handles long serv ref display
585  }
586  Display->Update(); // resurrected when Update() dropped from PlotOutput etc
587  Utilities->CallLogPop(652);
588 }
589 
590 // ---------------------------------------------------------------------------
591 void TTrain::UnplotTrain(int Caller)
592 {
593  // Note: If trouble is experienced with the PlotAlternativeTrackRouteGraphic functions remove them & test for train on a bridge and if so call Clearand..
594  if(!Plotted)
595  {
596  return;
597  }
598  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrain," + HeadCode);
599 
600  if(Straddle == MidLag)
601  {
602  if(MidElement > -1)
603  {
608  // to force plot of locked route marker, needed once only for the element
609  }
610  if(LagElement > -1)
611  {
616  // to force plot of locked route marker, needed once only for the element
617  }
618  }
619  else if(Straddle == LeadMidLag)
620  {
621  if(LeadElement > -1)
622  {
625  // to force plot of locked route marker, needed once only for the element
626  }
627  if(MidElement > -1)
628  {
633  // to force plot of locked route marker, needed once only for the element
634  }
635  if(LagElement > -1)
636  {
639  // to force plot of locked route marker, needed once only for the element
640  }
641  }
642  else if(Straddle == LeadMid)
643  {
644  if(LeadElement > -1)
645  {
650  // to force plot of locked route marker, needed once only for the element
651  }
652  if(MidElement > -1)
653  {
658  // to force plot of locked route marker, needed once only for the element
659  }
660  }
661  if(LeadElement > -1)
662  {
664  }
665  if(MidElement > -1)
666  {
668  }
669  if(LagElement > -1)
670  {
672  }
673  Plotted = false;
676  {
678  }
679  Display->Update();
680  // without this the screen 'blinks' at next Clearand... prob forces a full repaint for some reason
681  // resurrected when Update() dropped from PlotOutput etc
682  Utilities->CallLogPop(653);
683 }
684 
685 // ----------------------------------------------------------------------------
686 
687 void TTrain::UpdateTrain(int Caller)
688 /*
689  Note: Some changes made since comments written
690 
691  Brief:
692  Enter with Straddle defining train position wrt Lag, Mid & Lead elements. Is only MidLag at this point
693  on first entry at a continuation (with no train plotted), in all other cases it is either LeadMid (when train fully
694  on Lead & Mid elements) or LeadMidLag (when train straddling 3 elements).
695  Thereafter on entry Straddle = LeadMidLag or LeadMid; LeadMid if train fully on Mid & Lead elements, and
696  LeadMidLag if on Lag, Mid and Lead elements (back on lag, front on Lead, & middle 2 segments on Mid).
697  If enter with Straddle = LeadMid, then train is in effect in the first half of the next element, and moves half onto it after
698  the half time point has been passed. The values for the next element were set when the train was last updated when Straddle became
699  LeadMid from LeadMidLag. After the half time point has been passed Straddle is
700  changed to MidLag within the function and all elements moved down one, old Mid becomes
701  the new lag, old Lead becomes the new Mid, and a new Lead is obtained. Then the new positions are plotted, and finally Straddle is
702  incremented to reflect the position the train now occupies.
703 
704  Detail:
705  Set TrainFailurePending if all conditions met
706  Check whether stopped at a non-red signal, and if so reset StoppedAtSignal so train can move.
707  Check whether buffers at immediate exit, either when first enter the function or later, and set StoppedAtBuffers if so
708  and return.
709  If Straddle == LeadMid then train fully on Lead and Mid, so ready for a major update:-
710  If there's a LagElement (there will be but include check for good practice - next
711  function depends on it) Check whether DerailPending set - set during last GetLeadElement if appropriate but only acted on here when
712  train fully on offending point - Derail set and DerailPanding reset, train background
713  colour changed (note that BackgroundColour is a property of the train itself) then return.
714  If no derail pending reset Lag and Mid elements to the old Mid and Lead values, reset Straddle to MidLag, then set
715  the new LeadElement, which will be the next connected element (obtain using GetLeadElement) or -1 if the current
716  LeadElement is an exit continuation. During GetLeadElement the element at LeadElement is checked and if a stop
717  signal is found StoppedAtSignal is set to true, otherwise StoppedAtSignal is set to false. Also Derail is set
718  if LeadElement is a fouled trailing point.
719  Now, the train is moved on by one segment. Firstly the last BackgroundElement is set to LagElement, then the last
720  segment of the LagElement is unplotted (if there is a LagElement - may be entering at a continuation), by
721  replotting the last background segment and checking whether the element is a bridge or crossover with the other
722  track in a route, in which case the route colour is replotted.
723  Then, if Straddle == LeadMidLag (train will move completely off the element during this function), and the train
724  track is in a route, then all the train elements are removed from the route unless it's an autosig route. Normally only the
725  LeadElement will be in a route for a moving train, but when originally placed all elements may be in the route so check them all.
726  Note also that there may be two routes at a given element position, but only one of them is the correct one, so this
727  is identified prior to the removal. Also the TrainIDs are reset because the train will be fully off this element at the end of
728  the function. If Straddle == LeadMidlag and the element being left is a ContinuationExit the the TrainGone flag is set so the
729  train can be deleted by the calling function, and the function returns.
730  If the element is a signal in the train movement direction, then it is reset to red (Attribute = 0) and is replotted
731  to show the red aspect. Finally if element is a signal in the other direction it is replotted as it was - need to
732  plot individually because could have any aspect, the background bitmap that was picked up earlier contains just the
733  basic red aspect.
734 
735  Now all the array values are updated, but the [0] values are as yet invalid, these have to be obtained explicitly from
736  the new LeadElement later. The headcode graphics are updated so that it reads correctly - left to right & top to bottom,
737  regardless of direction, and with the correct front code colour.
738 
739  The new front segment background bitmap is now picked up and the graphic offsets set, and the segments are plotted.
740  No more unplotting is needed as all but the last segment are overwritten by later segments, and the new front
741  segment is just plotted, though the background bitmap at that location has to be picked up. Just where they are
742  plotted depends on the Straddle value, [0] is always on Lead, [1] is on Lead if Straddle == LeadMidLag or Mid if
743  Straddle == MidLag; [2] is always on Mid, and [3] is on Mid if Straddle == LeadMidLag or Lag if Straddle == MidLag.
744  Also prior to plotting the lead segment a crash check is made, and if true the Crashed flag is set and the
745  TrainCrashedInto value also set to the current TrainID - this is so it too becomes crashed and hence stopped.
746 
747  The Crashed flag is now checked, and if set the front headcode colour is changed to the same as the rest of the code,
748  and the background colour changed. Then the train that is crashed into is also set to Crashed, and its colours
749  changed similarly. The function then returns.
750 
751  If Crashed is not set then Straddle is incremented and the function returns.
752 */
753 
754 {
755  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UpdateTrain," + HeadCode);
756  UpdateCounter++;
757  // 100 counts = 5secs (used in splits to prevent too frequent length checks in front & rear splits)
758  if(UpdateCounter >= 100)
759  {
760  UpdateCounter = 0;
761  }
762  int RandRange = (TrainController->MTBFHours * 3600) / 53;
763 
764  // MTBFHours is in timetable clock hours, min value is 1 & max value is 9,999 (integer values on input)
765  // but double on use because it represents timetable clock time, so at 1/16 speed RandRange is * 16 (160,000 max) & at 16x speed its /16 (1/16 min)
766  // i.e MTBFHours is Input value/TTClockSpeed (conversion is done in InterfaceUnit)
767  if(int(TrainController->RandomFailureCounter) == (rand() % 1060))
768  // RandomFailureCounter value is fixed for a full cycle of train updates so this
769  // makes sure there's no bunching of failures as there is for a fixed comparison number
770  // or a small range of comparison numbers. True every 53 secs (real time) on average rand()
771  // gives a random number between 0 and 16384 (defined as RAND_MAX in stdlib.h)
772  {
773  if(!TrainFailed && !TrainOnContinuation(0) && (RandRange > 0) && (PowerAtRail > 1) && !((TrainMode == Timetable) && TimetableFinished)
774  && !Crashed && !Derailed && !((TrainMode == Signaller) && Stopped()))
775  // RandomFailureCounter resets to 0 every 53 secs, if RandRange is 0 then no failure rate is set - i.e. failure rate = 0
776  // don't fail if:
777  // (a) on a continuation (entering or leaving);
778  // (b) already failed;
779  // (c) power effectively zero (8000) min value for powered; 0.08 for 'no power';
780  // (d) train terminated;
781  // (e) crashed or derailed; or
782  // (f) under signaller control and stopped.
783  // (g) TreatPassAsTimeLocDeparture is true //added at v2.12.0
784  {
785  if((random(RandRange) == 0) && !TreatPassAsTimeLocDeparture)
786  // max value for RandRange is over 2x10^9
787  {
788  // here if failure due
789  TrainFailurePending = true;
790  // the failure occurs when PlotElements set to proper Lead & Mid Elements
791  }
792  }
793  }
794 /* dropped as it allows a train to stop on a half element - when reach (if Stopped()) at line 1310
795  if ((PowerAtRail < 1) && (EntrySpeed < 1)) // added at v2.4.0
796  {
797  StoppedWithoutPower = true;
798  }
799 */
800 // float TimeToExit; //added at v2.10.0 Removed these so original values retained - used when train on continuation
801 // THVShortPair ExitPair; //added at v2.10.0
802  int LockedVectorNumber;
803  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
804  // default values - these needed for route checker below
805  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
806 
808  {
810  }
811  if(Crashed || Derailed)
812  {
814  {
815  PlotTrain(7, Display);
816  // replotted every cycle because of level crossing crashes, otherwise a flashing level crossing wipes out half of the train
817  Display->Update();
818  }
819  OpTimeToAct = 0.0;
820  // need to set this here as wouldn't be calculated otherwise as return from UpdateTrain
821  Utilities->CallLogPop(1017);
822  return; // no further action, user has to remove or work around
823  }
824 
826  {
828  }
830  {
832  }
833  if(StoppedWithoutPower && (TrainMode == Signaller)) //added at v2.13.2 as this condition not covered - was shown as normal
834  {
836  }
838  // introduced at v1.2.0, formerly 'TimeTimeLocArrived = false' was included
839  // in the next condition 'if(!Stopped() && !SPADFlag)' which led to repeated arrival messages if signaller control allowed a train
840  // to move & then stop again at the same station
841  {
842  TimeTimeLocArrived = false;
843  }
844  if(!Stopped() && !SPADFlag && !TrainFailed)
845  {
847  }
848  // set or release StoppedAtBuffers if fully on 2 elements depending on LeadElement
849  // Note that if LeadElement == Buffers train must be facing the buffer so no need to check orientation
850 /* old version where force a stop at buffers regardless of speed
851  if((Straddle == LeadMid) && (LeadElement > -1) && (Track->TrackElementAt(, LeadElement).TrackType == Buffers)) StoppedAtBuffers = true;
852  else StoppedAtBuffers = false;
853 */
854 
855  // new version where crash if run into buffers
856  if(!Crashed)
857  {
858  if((Straddle == LeadMid) && (LeadElement > -1) && (Track->TrackElementAt(602, LeadElement).TrackType == Buffers))
859  {
860  if(ExitSpeedFull > 1)
861  {
862  Crashed = true;
866  // SendMissedActionLogs(3, -1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
867  // no need for missed action logs - will be sent when train removed
868  StoppedAtBuffers = false;
869  }
871  // stopped at location & stopped without power take precedence
872  {
873  StoppedAtBuffers = true;
874  }
875  else
876  {
877  StoppedAtBuffers = false;
878  }
879  }
880  else
881  {
882  StoppedAtBuffers = false;
883  }
884  }
885  else
886  {
887  StoppedAtBuffers = false;
888  }
889  // if crashed don't want stopped at buffers set
890 
891  // also crash if run into a level crossing that is changing or has barriers up
892  if(!Crashed)
893  {
894  if((Straddle == LeadMid) && (LeadElement > -1) && (ExitSpeedFull > 1))
895  {
896  int H = Track->TrackElementAt(873, LeadElement).HLoc;
897  int V = Track->TrackElementAt(874, LeadElement).VLoc;
898  if(Track->IsLCAtHV(40, H, V) && !Track->IsLCBarrierDownAtHV(2, H, V))
899  {
900  Crashed = true;
904  // no need for missed action logs - will be sent when train removed
905  }
906  }
907  }
909  {
911  }
912  // set or reset HoldAtLocationInTTMode (if true then actions are needed before train departs)
914  //if Command == "" then either TimeLoc or TimeTimeLoc so don't hold, and added last part at v2.12.0 so don't hold if have both command == pas and Treat... flag
915  {
916  HoldAtLocationInTTMode = true;
917  }
918  else if(TrainMode == Timetable)
919  {
920  HoldAtLocationInTTMode = false;
921  }
922  // in Signaller mode HoldAtLocationInTTMode not changed
923  // check if departure pending & set times unless already set
924  if(TrainMode == Timetable)
925  {
927  // && !StoppedAtBuffers) - drop this, set times whether or not at buffers
928  {
929  if((ActionVectorEntryPtr->Command != "pas") && (ActionVectorEntryPtr->DepartureTime > TDateTime(-1)) && (ActualArrivalTime > TDateTime(0)))
930  {
931  AnsiString ReasonArray[24] = {"a driver is awaited","a guard is awaited","of a medical emergency","of a technical problem","of a security issue",
932  "of a safety issue","of a disturbance","a train crew member has been taken ill","the driver has been taken ill","the guard has been taken ill",
933  "a report has been received concerning safety","a shoe has been lost under the train","of a reported theft",
934  "of an incident involving an animal","some luggage has been lost under the train","a minor repair is needed","a suspicious object has to be dealt with safely",
935  "a door is stuck open","additional stock has to be attached","a security alert","of a train fault","of an operating incident","safety checks are required",
936  "of a shortage of on train crew"};
937  //(ActionVectorEntryPtr->Command != "pas") added at v2.13.0 to rule out passes, though probably not needed
938  //(ActualArrivalTime > TDateTime(0)) added at v2.13.0 to ensure that it has been set and to dismiss trains that are present
939  //at start or have no departure time set.
940  NewDelay = 0; //section relating to random delays added at v2.13.0
941  TDateTime TimetableReleaseTime = TrainController->GetRepeatTime(0, ActionVectorEntryPtr->DepartureTime, RepeatNumber, IncrementalMinutes); //Timetable value
942  TDateTime DwellTime = TimetableReleaseTime - ActualArrivalTime; //Timetable value
943 //diagnostic additions
944 //double aa = double(ActionVectorEntryPtr->DepartureTime) * 24;
945 //double bb = double(ActualArrivalTime) * 24;
946 //double cc = double(TimetableReleaseTime) * 24;
947 //double dd = double(ArrivalMinDwellTime); //secs
948  if(DwellTime < TDateTime(ArrivalMinDwellTime / 86400))
949  {
950  DwellTime = TDateTime(ArrivalMinDwellTime / 86400);
951  }
952  int randval = random(10000);
953  if(randval != 0) //if randval == 0 or DelayMode == Nil then NewDelay will be 0 as set above
954  {
955  if(Utilities->DelayMode == Minor)
956  {
957  if(randval < Utilities->MinorDelayCutoff)
958  {
959  NewDelay = Utilities->MinorDelayFactor * log(Utilities->MinorDelayCutoff/randval);//minutes (confusingly log in C++Builder gives the natural logarithm)
960  }
961  }
962  else if(Utilities->DelayMode == Moderate)
963  {
964  if(randval < Utilities->ModerateDelayCutoff)
965  {
967  }
968  }
969  else if(Utilities->DelayMode == Major)
970  {
971  if(randval < Utilities->MajorDelayCutoff)
972  {
974  }
975  }
976  }
977 //NewDelay = 5; //test
978  if(double(TrainController->TTClockTime) <= (Utilities->LastDelayTTClockTime + 5.0/1440.0)) //if within 5 mins of last delay for any train
979  { //then don't delay. Added at v2.13.0
980  NewDelay = 0;
981  }
982  if(NewDelay < 1)
983  {
984  NewDelay = 0;
985  }
986  if(NewDelay < double(DwellTime) * 1440) //if less than scheduled dwell time then no additional delay
987  {
988  NewDelay = 0;
989  }
990  else
991  {
992  NewDelay -= double(DwellTime) * 1440;//reduce delay by dwell time
993  }
994  if(DelayedRandMins > 0)
995  {
996  DelayedRandMins -= double(DwellTime) * 1440;//reduce knock-on random delay by dwell time
997  }
998  if(DelayedRandMins < 0)
999  {
1000  DelayedRandMins = 0;//can't be less than zero
1001  }
1003  {
1004  NewDelay -= DelayedRandMins; //NewDelay is the additional delay over and above the existing knock-on delay (from earlier random delays)
1005  //the formula above already includes knock-on effects
1006  DelayedRandMins += NewDelay; //the new total delay, knock-on + additional
1007 // CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //don't add here, add when depart, else this value can be > late mins
1008  }
1009  else
1010  {
1011  NewDelay = 0;
1012 // CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //as above
1013  }
1014  ReleaseTime = LastActionTime + TDateTime(NewDelay / 1440); //earliest possible release time
1015 // if(NewDelay < (ArrivalMinDwellTime / 60)) //less than the min dwell time
1016  if(ReleaseTime < LastActionTime + TDateTime(30.0 / 86400)) //30sec delay between actions
1017  {
1018  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1019  }
1020  if(ReleaseTime < ActualArrivalTime + TDateTime(ArrivalMinDwellTime / 86400)) //lowest value of ArrivalMinDwellTime is 30s
1021  {
1022  ReleaseTime = ActualArrivalTime + TDateTime(ArrivalMinDwellTime / 86400);
1023  }
1024  if(ReleaseTime < TimetableReleaseTime)
1025  {
1026  ReleaseTime = TimetableReleaseTime; //back to correct time
1027  NewDelay = 0;
1028  DelayedRandMins = 0;
1029  }
1030  if(DelayedRandMins > double(ReleaseTime - TimetableReleaseTime) * 1440)
1031  {
1032  DelayedRandMins = double(ReleaseTime - TimetableReleaseTime) * 1440; //reduce this if time has been made up
1033  }
1034 
1035  if(DelayedRandMins < NewDelay) //may be if reduced above, but if so need to reduce NewDelay also
1036  {
1038  }
1039  //may be possible to simplify all the above but as it seems to work ok leave as is
1040  if(int(NewDelay) > 0) //additional delay over and above knock-on effects from earlier random delays
1041  {
1043  if(int(NewDelay) == 1)
1044  {
1045  Display->WarningLog(12, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": " + HeadCode + " delayed at " +
1046  ActionVectorEntryPtr->LocationName + " by 1 minute");
1047  PerfLogForm->PerformanceLog(18, Utilities->Format96HHMMSS(TrainController->TTClockTime) + " WARNING: " + HeadCode + " delayed at " +
1048  ActionVectorEntryPtr->LocationName + " by 1 minute because of a minor technical issue");
1049  TrainController->StopTTClockMessage(140, HeadCode + " delayed at " +
1050  ActionVectorEntryPtr->LocationName + " by 1 minute because of a minor technical issue");
1051  }
1052  else
1053  {
1054  Display->WarningLog(11, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": " + HeadCode + " delayed at " +
1055  ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) + " minutes");
1056  if(NewDelay >= 10) //give variable reasons for >= 10 mins
1057  {
1058  int randval2 = rand() % 24; //24 reasons
1059  AnsiString Reason = ReasonArray[randval2];
1061  HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1062  " minutes because " + Reason);
1063  TrainController->StopTTClockMessage(141, HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1064  " minutes because " + Reason);
1065  }
1066  else
1067  {
1069  HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1070  " minutes because of a minor problem");
1071  TrainController->StopTTClockMessage(142, HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1072  " minutes because of a minor problem");
1073  }
1074  }
1075  }
1076  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1077  DepartureTimeSet = true; //reset ActualArrivalTime, ArrivalMinDwellTime and NonDefaultMinDwellTimeFlag when depart.
1078  }
1079  else if(ActionVectorEntryPtr->DepartureTime > TDateTime(-1)) //as was, for trains that don't have an arrival time set
1080  {//if have skipped to a new service then DepartureTime will be set (in above segment when earlier train arrived)
1081  //but ArrivalTime won't be set as it is reset to 0 at end of above segement when earlier train arrived, so this segement
1082  //will run without any random delays which might cause timing errors from mixing modifications but at least shouldn't crash.
1083  NewDelay = 0;
1084  DelayedRandMins = 0;
1086  if(ReleaseTime <= LastActionTime + TDateTime(30.0 / 86400))
1087  {
1088  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1089  }
1090  if(ReleaseTime <= ActualArrivalTime + TDateTime(ActionVectorEntryPtr->MinDwellTime / 86400))
1091  {
1093  }
1094  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1095  DepartureTimeSet = true;
1096  }
1097  else if((ActionVectorEntryPtr->Command == "pas") && TreatPassAsTimeLocDeparture) //new segment at v2.12.0 to treat a pass as a departure
1098  {//for when skip to a new service at a pass location. As above this also avoids any random delays, and will avoid above segment because
1099  //departure time isn't set - it's an event time. Again random delays in this situation might cause timing errors
1100  //from mixing modifications. Note that can't set a min dwell time for pas commands
1101  NewDelay = 0;
1102  DelayedRandMins = 0;
1104  if(ReleaseTime <= LastActionTime + TDateTime(30.0 / 86400))
1105  {
1106  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1107  }
1108  if(ReleaseTime <= ActualArrivalTime + TDateTime(ArrivalMinDwellTime / 86400))
1109  {
1110  ReleaseTime = ActualArrivalTime + TDateTime(ArrivalMinDwellTime / 86400);
1111  }
1112  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1113  DepartureTimeSet = true;
1114  }
1115  }
1116  }
1117  if(TrainController->OpTimeToActUpdateCounter == 0)// && TrainController->OpActionPanelVisible) removed last condition so always calc TimeToExit
1118  {
1119  OpTimeToAct = CalcTimeToAct(0, TimeToExit, ExitPair); // called after ReleaseTime set
1120 // this->TimeToExit = TimeToExit; don't need these as values updated directly
1121 // this->ExitPair = ExitPair;
1122  // calculate every 1 sec (in real time, not timetable time) for all trains
1123  }
1124  // check if being held at location pending any actions & deal with them if time appropriate & >= 30s since LastActionTime
1125  if(TrainMode == Timetable)
1126  {
1127  if((ActionVectorEntryPtr->Command != "Frh") && (ActionVectorEntryPtr->Command != "Frh-sh"))
1128  {
1129  RemainHereLogNotSent = true;
1130  }
1132  {
1133  // ignore TimeLoc & TTLoc departures
1134  // Action logs given in functions
1136  LastActionTime + TDateTime(30.0 / 86400)))
1137  {
1138  if(ActionVectorEntryPtr->Command == "fsp")
1139  {
1140  // added for v1.3.2 because when add new train to TrainVector 'this' address likely invalidated, hence make no more changes to
1141  //'this' train. Next clock cycle will deal with any required changes
1142  FrontTrainSplit(0);
1143  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1144  { //this is checked before each return
1145  TrainHasFailed(0);
1146  }
1147  Utilities->CallLogPop(2041);
1148  return;
1149  }
1150  else if(ActionVectorEntryPtr->Command == "rsp")
1151  {
1152  // added for v1.3.2 because when add new train to TrainVector 'this' address likely invalidated, hence make no more changes to
1153  //'this' train. Next clock cycle will deal with any required changes
1154  RearTrainSplit(0);
1155  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1156  {
1157  TrainHasFailed(1);
1158  }
1159  Utilities->CallLogPop(2042);
1160  return;
1161  }
1162  else if(ActionVectorEntryPtr->Command == "Fjo")
1163  {
1164  FinishJoin(0);
1165  }
1166  else if(ActionVectorEntryPtr->Command == "jbo")
1167  {
1168  JoinedBy(0);
1169  }
1170  else if(ActionVectorEntryPtr->Command == "cdt")
1171  {
1172  ChangeTrainDirection(0, false);
1173  }
1174  else if(ActionVectorEntryPtr->Command == "dsc")
1175  {
1176  Description = ActionVectorEntryPtr->NewDescription; //changed at v2.16.1
1180  }
1181  else if(ActionVectorEntryPtr->Command == "cms") //added at v2.21.0 for change of max speed
1182  {
1187  }
1188  else if(ActionVectorEntryPtr->Command == "Fns")
1189  {
1190  NewTrainService(0, false);
1191  }
1192  else if(ActionVectorEntryPtr->Command == "Frh")
1193  {
1194  RemainHere(0);
1195  }
1196  else if(ActionVectorEntryPtr->Command == "Fer")
1197  {
1198  TimetableFinished = true;
1199  }
1200  // other aspects of 'Fer' dealt with in TTrain::HasTrainGone()
1201  else if(ActionVectorEntryPtr->Command == "F-nshs")
1202  {
1204  }
1205  else if(ActionVectorEntryPtr->Command == "Frh-sh")
1206  {
1207  RepeatShuttleOrRemainHere(0, false);
1208  }
1209  else if(ActionVectorEntryPtr->Command == "Fns-sh")
1210  {
1212  }
1213 /*
1214  F-nshs (FNSShuttle) = Finish New Service (Shuttle) = finish, form new shuttle service in same direction, details =
1215  shuttle headcode (no train creation)
1216  Frh-sh (TimeCmdHeadCode) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done
1217  remain here
1218  Fns-sh (FSHNewService) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done
1219  form new service via Sns-fsh using the NonRepeatingShuttleLinkHeadCode
1220 */
1221  }
1222  }
1223  else
1224  {
1226  {
1228  }
1229  }
1230  }
1231  if(TrainMode == Timetable)
1232  {
1233  if(StoppedAtBuffers)
1234  {
1235  // error if buffers (& element before it) not at a location, or if buffer location different to ActionVectorEntryPtr location
1236  // if buffer location same as ActionVectorEntryPtr location & not Frh then error will be given for inability to depart
1237  AnsiString BufferLocation = Track->TrackElementAt(604, LeadElement).ActiveTrackElementName;
1238  if(BufferLocation == "")
1239  {
1241  }
1242  AnsiString ExpectedLocation = ActionVectorEntryPtr->LocationName;
1243  if((BufferLocation == "") || (BufferLocation != ExpectedLocation))
1244  {
1248  {
1250  // SendMissedActionLogs(-1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
1251  // Drop missed actions so user can still use sig mode to get back on track
1253  }
1254  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1255  {
1257  TrainHasFailed(2);
1258  }
1259  Utilities->CallLogPop(1020);
1260  return;
1261  }
1262  else if((BufferLocation != "") && (BufferLocation == ExpectedLocation) && DepartureTimeSet && !RevisedStoppedAtLoc() && (TrainController->TTClockTime >
1263  ReleaseTime))
1264  {
1267  {
1270  // SendMissedActionLogs(-1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
1271  // Drop missed actions so user can still use sig mode to get back on track
1273  }
1274  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1275  {
1277  TrainHasFailed(3);
1278  }
1279  Utilities->CallLogPop(1397);
1280  return;
1281  }
1282  }
1283  else
1284  {
1286  }
1287  }
1288  else
1289  {
1291  }
1292  if(TrainMode == Timetable)
1293  {
1295  {
1297  }
1299  {
1301  }
1302  }
1303  // Pick up element next to the train front (if exists) to check for calling-on, restart after a cleared signal, or
1304  // restart after stopped for train in front
1305  int NextElementPosition, NextEntryPos;
1306 
1307  if(LeadElement > -1) // if an exit continuation then not set
1308  {
1309  if((Track->TrackElementAt(186, LeadElement).TrackType != Points) || ((LeadEntryPos != 0) && (LeadEntryPos != 2)))
1310  {
1312  }
1313  else if((Track->TrackElementAt(187, LeadElement).TrackType == Points) && ((LeadEntryPos == 0) || (LeadEntryPos == 2)))
1314  {
1315  if(Track->TrackElementAt(188, LeadElement).Attribute == 0)
1316  {
1317  LeadExitPos = 1;
1318  }
1319  else
1320  {
1321  LeadExitPos = 3;
1322  }
1323  }
1324  NextElementPosition = Track->TrackElementAt(189, LeadElement).Conn[LeadExitPos];
1325  NextEntryPos = Track->TrackElementAt(190, LeadElement).ConnLinkPos[LeadExitPos];
1326  }
1327  else
1328  {
1329  NextElementPosition = -1;
1330  NextEntryPos = -1;
1331  }
1332  if((NextElementPosition > -1) && (NextEntryPos > -1))
1333  // may be buffers or continuation so need this check
1334  {
1335 /*
1336  Check whether calling-on conditions met:-
1337  a) approaching train has stopped at a signal but not at a location;
1338  b) if there is a facing train at the station, it is being held appropriately (must be awaiting a join (Fjo or jbo) or a
1339  change of direction (cdt), remaining here (Frh), or under signaller control);
1340  c) at least 1 platform available for the approaching train;
1341  d) points (if any) set for direct route into platform;
1342  e) approaching train is to stop at station;
1343  f) no more facing signals between train and platform;
1344  g) [dropped g]
1345  h) train in front preventing route being set far enough to release stop signal;
1346  i) train in front not exiting at continuation;
1347  j) signal must be within 4km of the stop platform;
1348  k) [dropped (k), now can set a reoute or part route into platform so can set points more easily];
1349  l) no existing route conflicts with the route into the platform; and
1350  m) not failed or without power (these added at v2.10.0)
1351  If all OK & route or part route not already set then set an unrestricted route into the station (just to the first platform), to prevent point changing or
1352  other route conflicts - if a partial route set than can still change points outside the route or have a route conflict if another route is set.
1353 */
1354  if(TrainMode == Timetable)
1355  {
1356  if(CallingOnAllowed(0)) //returns false if failed or no power (modified afer v2.9.2)
1357  {
1358  CallingOnFlag = true;
1359  PlotTrainWithNewBackgroundColour(1, clCallOnBackground, Display); // calling-on background
1360  }
1361  else
1362  {
1363  if(CallingOnFlag) //TrainHasFailed sets this flag to false (at v2.10.0)
1364  {
1365  if(!TrainFailed) //shouldn't be needed but include for safety at v2.10.0
1366  {
1368  }
1369  }
1370  CallingOnFlag = false;
1371  }
1372  }
1373  if(StoppedAtSignal && ((Track->TrackElementAt(191, NextElementPosition).Attribute > 0) || AllowedToPassRedSignal) && !TrainFailed && !RevisedStoppedAtLoc())
1374  {
1375  //'&& !StoppedAtLocation' added at v2.7.0 as if had been stopped at signal before tt control restored then background colour changed to normal when signal changed from red
1376  // reset PassRedSignal when reached half-way point in next element, if reset here then SetTrainMovementValues
1377  // sets StoppedAtSignal again & train doesn't move
1378  StoppedAtSignal = false;
1379  // need to recalculate exit times since old entry time expired. Straddle now at MidLag with front of train on MidElement
1380  // hence use MidElement for the calculation so same as would have been used if signal not red, when Straddle was
1381  // LeadMidLag and front of train was on LeadElement (after the current move)
1383  EntrySpeed = 0;
1385  FirstHalfMove = true;
1386  SetTrainMovementValues(4, NextElementPosition, NextEntryPos);
1387  // NextElement is the element to be entered
1388  }
1389  if((LeadElement > -1) && (LeadExitPos > -1))//this section added at v2.18.0
1390  {
1391  int NextPos = Track->TrackElementAt(649, LeadElement).Conn[LeadExitPos];
1392  if(NextPos > -1)
1393  {
1394  int NextEntryPos = Track->TrackElementAt(1674, LeadElement).ConnLinkPos[LeadExitPos];
1395  if(Track->OtherTrainOnTrack(1, NextPos, NextEntryPos, TrainID))
1396  // true if another train on NextEntryPos track whether bridge or not
1397  {
1398  TrainInFront = true;
1399  }
1400  else
1401  {
1402  TrainInFront = false;
1403  }
1404  }
1405  }
1406  if((TrainMode == Signaller) && StoppedForTrainInFront && !TrainInFront) //train not directly in front //added at v2.23.0 without this a train under signaller control
1407  { //won't move until a train in front has passed the next signal, this is ok for being called on but under sig control the train should be able to move when required to do so
1408  StoppedForTrainInFront = false;
1409  }
1411  {
1412  if(ClearToNextSignal(0))
1413  {
1414  StoppedForTrainInFront = false;
1415  TrainInFront = false;
1416  BeingCalledOn = false;
1417  EntrySpeed = 0;
1419  FirstHalfMove = true;
1420  SetTrainMovementValues(16, NextElementPosition, NextEntryPos);
1421  }
1422  }
1423  }
1424  if(Stopped() && TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1425  {
1426  TrainHasFailed(4);
1427  Utilities->CallLogPop(1097);
1428  return;
1429  }
1430  if((Straddle == MidLag) && (LeadElement != -1))
1431  // later check only for Straddle == LeadMid, so need this check here for initial train start
1432  {
1434  }
1435 /* Logic below as follows: This check is made to allow a restart if had StoppedAtLocation or StoppedForTrainInFront or
1436  both but potentially able to restart (i.e. not at buffers, not crashed, not derailed, not held at location, departure
1437  time due, no train in front now & no other stop condition). Note that can be StoppedForTrainInFront when not at a
1438  location since this is set in SetTrainMovementValues whenever a train has zero EntrySpeed and there is a train in front,
1439  which could be when start as Snt.
1440  If StoppedForTrainInFront but not StoppedAtLocation then need to set TRSTime high so pink not plotted, and ReleaseTime
1441  low so can restart if appropriate. BeingCalledOn was set so that when train stopped at a station it wouldn't restart
1442  until the line was clear of trains up to the next signal. Hence check whether BeingCalledOn & if so set
1443  StoppedForTrainInFront, this ensures two things - that the restart check is carried out at each cycle and also that
1444  a restart won't happen until the line is clear to the next signal, regardless of whether or not the ReleaseTime has been
1445  reached.
1446  Then check if TRS time reached & change background to pink if so, & check if release time reached & if so change
1447  background to white and clear StoppedAtLocation. Then make check of station name, and recheck StoppedForTrainInFront,
1448  if it's set check if ClearToNextSignal and if so clear StoppedForTrainInFront & BeingCalledOn. If not ClearToNextSignal
1449  then return. If either not StoppedForTrainInFront or ClearToNextSignal then restart, calling SetTrainMovementValues &
1450  sending a message to the performancelog.
1451 */
1452 
1453  if(TrainMode == Timetable)
1454  {
1456  {
1457 // if(BeingCalledOn) //dropped when added TrainInFront at v2.18.0
1458 // {
1459 // TrainInFront = true;
1460 // }
1461  SetTrainMovementValues(25, LeadElement, LeadEntryPos); //this is purely to set StoppedForTrainInFront as needed before formal call //added at v2.19.1
1462  if((TrainController->TTClockTime >= TRSTime) && (PowerAtRail >= 1) && !StoppedForTrainInFront) //added later conditions at v2.19.1
1463  {
1465  }
1466  else //added at v2.14.0 as if a train ready to depart (pink b'gnd) taken under sig control then restored to tt control b'gnd stayed pink,
1467  { //even though release time now 30 seconds after tt control restored
1469  }
1471  {
1472  // value updated at every scheduled departure & arrival
1473  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0 moved here from after 'StoppedAtLocation = false' at v2.19.1
1474  {
1475  StoppedWithoutPower = true;
1476  }
1477  if(!StoppedWithoutPower && !StoppedForTrainInFront) //added at v2.19.1, don't change colour if no power or train in front
1478  {
1480  }
1481  AnsiString StationName;
1483  {
1485  }
1487  {
1489  }
1490  else
1491  {
1492  throw Exception("Error - Stopped at through station but neither lead nor mid elements have a name");
1493  }
1494  EntrySpeed = 0;
1496  if(!StoppedWithoutPower && !StoppedForTrainInFront) //added at v2.19.1 & all below put in conditional block - due to JasonB false departure log in email of 21/02/24
1497  { //= extended it to StoppedForTrainInFront as well for same reasons
1498  ZeroPowerDepartMessage = false;
1499  TrainInFrontMessage = false;
1500  FirstHalfMove = true;
1501  StoppedAtLocation = false;
1502  if((NextElementPosition > -1) && (NextEntryPos > -1))
1503  // condition check added for SloughIECC error reported by James U
1504  {
1505  if((Track->TrackElementAt(720, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1506  (Track->TrackElementAt(721, NextElementPosition).Attribute == 0))
1507  {
1508  StoppedAtSignal = true;
1510  // TrainController->LogActionError(40, HeadCode, "", SignalHold, Track->TrackElementAt(755, NextElementPosition).ElementID);
1511  }
1512  }
1514  {
1515  TimeTimeLocArrived = false;
1516  LogAction(27, HeadCode, "", Depart, StationName, "", ActionVectorEntryPtr->DepartureTime, false);
1517  // no warning for TimeTimeLoc departure
1518  }
1519  else if(TreatPassAsTimeLocDeparture) //added at v2.12.0 so late/early/on time mins recorded accurately
1520  {
1521  LogAction(36, HeadCode, "", Depart, StationName, "", ActionVectorEntryPtr->EventTime, ActionVectorEntryPtr->Warning); //EventTime because the real event is a pass
1522  }
1523  else //must be TimeLoc departure
1524  {
1526  }
1527  TreatPassAsTimeLocDeparture = false; //added at v2.12.0, reset after train departs
1528  ActualArrivalTime = TDateTime(0); //added these 3 at v2.23.0 //reset to zero, note that only run through this section once per arrival
1529  ArrivalMinDwellTime = 30.0; //reset to default value
1530  NonDefaultMinDwellTimeFlag = false; //reset
1531  DepartureTimeSet = false;
1532  // no need to set LastActionTime for a departure
1533  //deal here with departure pointer change, increment if SkippedDeparture
1534  CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //only add these after late mins added (in LogAction)
1535 
1536  if(SkippedDeparture) //only deal with this when have power
1537  {
1540  TrainSkippedEvents = 0;
1541  SkippedDeparture = false;
1542  SkipPtrValue = 0;
1543  ActionsSkippedFlag = false;
1544  }
1545  else
1546  {
1548  }
1549  // advance pointer beyond departure action - (this line (& LogAction) used to be at the end - see
1550  // note
1551 /*
1552  Note: If train stops at station after call on with a TimeTimeLoc loaded, and before the normal stop point, then when
1553  SetTrainMovementValues called it assumes a stop at the stop point because the ActionVectorEntryPtr points to a name
1554  when NameInTimetableBeforeCDT is called and the stop positions are valid. So next element train movement is based on
1555  this calculation. However, when the departure time check is made (it is during this function when SetTrainMovementValues
1556  is called), the ActionVectorEntryPtr is advanced at the end past the departure location, so at the next element when
1557  SetTrainMovementValues is called again, all is normal, i.e. the train doesn't stop again at the location. But to cure
1558  the problem move the ActionVectorEntryPtr increment to before SetTrainMovementValues.
1559 */
1561  {
1562  StoppedAtBuffers = true;
1563  }
1564  else
1565  // if buffers or no power, don't set values
1566  {
1568  {
1569  SetTrainMovementValues(12, NextElementPosition, NextEntryPos);
1570  // NextElement is the element to be entered
1571  }
1572  else
1573  {
1575  // use LeadElement for an exit continuation
1576  }
1577  }
1578  }
1579  else if(StoppedForTrainInFront) // StoppedForTrainInFront, don't advance AVPtr - added at v2.19.1
1580  {
1581  if(!TrainInFrontMessage)
1582  {
1583  TrainController->LogActionError(67, HeadCode, "", FailTrainInFront, StationName);
1584  TrainInFrontMessage = true;
1585  }
1586  }
1587  else //no power, don't advance AVPtr - added at v2.19.1
1588  {
1590  {
1592  ZeroPowerDepartMessage = true;
1593  }
1594  }
1595 
1596  }
1597  }
1598  }
1599  if(Straddle == LeadMidLag) //train on a half element
1600  {
1602  {
1603  Utilities->CallLogPop(654);
1604  return;
1605  }
1606  }
1607  else //train fully on 2 elements
1608  {
1610  {
1611  Utilities->CallLogPop(655);
1612  return;
1613  }
1614  }
1615  if((LeadElement > -1) && (MidElement > -1))
1616  {
1618  {
1619  // don't allow to stop if exiting at a continuation as causes problems if try to change direction
1620  // if entering at continuation & LeadElement is a continuation then MidElement will be -1
1621  //don't need to check for MidElement being continuation because popup menu won't show when exiting at continuation so SignallerStoppingFlag can't be set
1622  SignallerStoppingFlag = false;
1623  StepForwardFlag = false;
1624  }
1625  }
1626  if(Stopped())
1627  // this is what prevents another movement if the train is stopped
1628  {
1629  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1630  {
1631  TrainHasFailed(5);
1632  }
1633  BrakeRate = 0;
1634  Utilities->CallLogPop(656);
1635  return;
1636  }
1637 
1638  // HERE WHEN READY FOR NEXT MOVE
1639 
1640  //added at v2.10.0 to set SPADFlag if red signal immediately ahead (as it will be if in a locked route)
1641  //check if due to run past a red signal & if so set SPADFlag (SetTrainMovementValues & its SPAD check only called when arrive fully on 2 elements)
1642  if(Straddle == LeadMid) //fully on 2 elements
1643  {
1644  if(LeadElement > -1)
1645  {
1646  if(Track->TrackElementAt(1402, LeadElement).Conn[LeadExitPos] > -1)
1647  {
1649  if(TIF.TrackType == SignalPost)
1650  {
1651  int TIFEntryPos = Track->TrackElementAt(1405, LeadElement).ConnLinkPos[LeadExitPos];
1652  int TIFExitPos = 0;
1653  if(TIFEntryPos == 0)
1654  {
1655  TIFExitPos = 1;
1656  }
1657  if((TIF.Config[TIFExitPos] == Signal) && TIF.Attribute == 0 && (ExitSpeedHalf > 1) && !AllowedToPassRedSignal && !TIF.CallingOnSet) //use ExitSpeedHalf as may have been stopped at signal so entryspeed is 0
1659  {
1660  SPADFlag = true; // user has to intervene to reset & restart after spad
1661  }
1662  }
1663  }
1664  }
1665  }
1666 
1667  // check for train in front & if so stop at next access (when train fully on element next to train)
1668  if((TrainMode == Signaller) && (Straddle == LeadMidLag))
1669  // SetTrainMovementValues brakes & stops signaller mode train for a train in front using local
1670  // variable TrainInFrontInSignallerModeFlag
1671  {
1672  if((LeadElement > -1) && (LeadExitPos > -1))
1673  {
1674  int NextPos = Track->TrackElementAt(1672, LeadElement).Conn[LeadExitPos];
1675  int NextEntryPos = Track->TrackElementAt(1675, LeadElement).ConnLinkPos[LeadExitPos];
1676  if(Track->OtherTrainOnTrack(16, NextPos, NextEntryPos, TrainID))
1677  // true if another train on NextEntryPos track whether bridge or not
1678  {
1679  TrainInFront = true;
1680  }
1681  else
1682  {
1683  TrainInFront = false;
1684  }
1685  }
1686  }
1687  if((Straddle == LeadMid) && SPADFlag)
1688  // give message + plot background when ready to move half past the signal
1689  {
1690  if(NextElementPosition > -1)
1691  {
1692  if((Track->TrackElementAt(662, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1693  (Track->TrackElementAt(663, NextElementPosition).Attribute == 0))
1694  {
1695  AnsiString LocID = AnsiString(Track->TrackElementAt(664, NextElementPosition).ElementID);
1697  // if goes past 2 signals then give message twice
1699  }
1700  }
1701  }
1702  if(Straddle == LeadMidLag)
1703  // During this function train moves fully onto 2 elements, Lead & Mid, so set next 2 moves from here for the element after Lead
1704  {
1705  // if SPADFlag set allow to keep moving until signal obscured before setting background colour, & stop only when ExitSpeedFull is 0
1706  if(SPADFlag)
1707  {
1708  if(ExitSpeedFull == 0)
1709  {
1710  StoppedAfterSPAD = true;
1711  // but don't want to stop until have moved fully onto element, hence stop test is before this check
1712  }
1713  }
1715  {
1716  if(ExitSpeedFull == 0)
1717  {
1718  // only reach here when will stop on LeadMid, because SetTrainMovementValues called after this (i.e. ExitSpeedFull becomes 0 if not 0 now
1719  // after this test), and Straddle == LeadMidLag so not accessed at the half-move point, hence only reached at the full move
1720  // point when the speed is 0. So, colour change won't occur until fully stopped (early in UpdateTrain()), and the log message
1721  // is sent at the right time and once only.
1722  SignallerStopped = true;
1723  // but don't want to stop until have moved fully onto element, hence stop test is before this check
1724  StepForwardFlag = false;
1725  SignallerStoppingFlag = false;
1726  TTrackElement TE;
1727  AnsiString Loc = "";
1728  bool LocNamed = false;
1729  if(LeadElement > -1)
1730  {
1731  TE = Track->TrackElementAt(782, LeadElement);
1732  if(TE.ActiveTrackElementName != "")
1733  {
1734  Loc = TE.ActiveTrackElementName;
1735  LocNamed = true;
1736  }
1737  else
1738  {
1739  Loc = "track element " + TE.ElementID;
1740  }
1741  }
1742  if((MidElement > -1) && !LocNamed)
1743  {
1744  TE = Track->TrackElementAt(783, MidElement);
1745  if(TE.ActiveTrackElementName != "")
1746  {
1747  Loc = TE.ActiveTrackElementName;
1748  LocNamed = true;
1749  }
1750  else if(Loc == "")
1751  {
1752  Loc = "track element " + TE.ElementID;
1753  }
1754  }
1755  if(Loc == "")
1756  {
1757  Loc = "outside railway";
1758  // must have stopped after left at a continuation (because both lead & mid == -1)
1759  }
1760  else
1761  {
1762  Loc = "at " + Loc;
1763  }
1764  LogAction(30, HeadCode, "", SignallerStop, Loc, "", TrainController->TTClockTime, false); // false for warning
1765  }
1766  }
1767  if(LeadElement > -1) // if an exit continuation then not set
1768  {
1769  if((Track->TrackElementAt(202, LeadElement).TrackType != Points) || ((LeadEntryPos != 0) && (LeadEntryPos != 2)))
1770  {
1772  }
1773  else if((Track->TrackElementAt(203, LeadElement).TrackType == Points) && ((LeadEntryPos == 0) || (LeadEntryPos == 2)))
1774  {
1775  if(Track->TrackElementAt(204, LeadElement).Attribute == 0)
1776  {
1777  LeadExitPos = 1;
1778  }
1779  else
1780  {
1781  LeadExitPos = 3;
1782  }
1783  }
1784  NextElementPosition = Track->TrackElementAt(205, LeadElement).Conn[LeadExitPos];
1785  NextEntryPos = Track->TrackElementAt(206, LeadElement).ConnLinkPos[LeadExitPos];
1786  }
1787  else
1788  {
1789  NextElementPosition = -1;
1790  NextEntryPos = -1;
1791  }
1794  FirstHalfMove = true; //will be when finished the move onto 2 elements during this function
1795 
1796  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
1797  {
1798  StoppedWithoutPower = true;
1799  }
1800  if((NextElementPosition > -1) && (NextEntryPos > -1) && !SPADFlag)
1801  // may be buffers or continuation. SPADFlag added at v2.1.0
1802  // so don't override the SPAD colour & don't set StoppedAtSignal
1803  {
1804  if((Track->TrackElementAt(207, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1805  (Track->TrackElementAt(208, NextElementPosition).Attribute == 0) && (ExitSpeedFull < 1) && !RevisedStoppedAtLoc())
1806  {
1807  StoppedAtSignal = true;
1808  if(!StoppedWithoutPower)
1809  // leave background as is if no power, but set StoppedAtSignal
1810  {
1812  }
1813  // TrainController->LogActionError(41, HeadCode, "", SignalHold, Track->TrackElementAt(756, NextElementPosition).ElementID);
1814  }
1815  }
1816  if(!Stopped())
1817  {
1818  if((NextElementPosition > -1) && (NextEntryPos > -1))
1819  // may be buffers or continuation (skip SetTrainMovementValues if buffers, if
1820  // a stop element that isn't buffers - e.g. station, then will skip the calcs
1821  // during SetTrainMovementValues to avoid trying to divide by zero - see that
1822  // function for fuller explanation
1823  {
1824  SetTrainMovementValues(8, NextElementPosition, NextEntryPos);
1825  // NextElement is the element to be entered
1826  }
1827  // follow the continuation exits:-
1828  else if((LeadElement > -1) && (Track->TrackElementAt(209, LeadElement).TrackType == Continuation))
1829  {
1831  // Use LeadElement for calcs if lead is a continuation
1832  }
1833  else if((MidElement > -1) && (Track->TrackElementAt(210, MidElement).TrackType == Continuation))
1834  {
1836  // Use MidElement for calcs if mid is a continuation
1837  }
1838  else if((LagElement > -1) && (Track->TrackElementAt(211, LagElement).TrackType == Continuation))
1839  {
1841  // Use LagElement for calcs if lag is a continuation
1842  }
1843  }
1844  // remove route elements if not autosigs - this section moved from below, was under LagElement > -1 condition but needs to cover LagElement == -1
1845  if((AllRoutes->GetRouteTypeAndGraphics(2, LeadElement, LeadEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute))
1846  // Trains may not be in a route
1847  // Since Straddle = LeadMidLag at this point the train is going to move fully off the existing Lag & fully onto existing Lead element during this function
1848  {
1849  // NB if LeadElement == -1 then the above test returns NoRoute
1850  int TempH = Track->TrackElementAt(213, LeadElement).HLoc;
1851  int TempV = Track->TrackElementAt(214, LeadElement).VLoc;
1852  int TempELink = Track->TrackElementAt(215, LeadElement).Link[LeadEntryPos];
1853  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1854  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(10, TempH, TempV, SecondPair);
1855  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(143, FirstPair.first).GetFixedPrefDirElementAt(153,
1856  FirstPair.second).GetELink() == TempELink))
1857  {
1858  AllRoutes->RemoveRouteElement(10, TempH, TempV, TempELink);
1859  }
1860  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(144, SecondPair.first).GetFixedPrefDirElementAt(154,
1861  SecondPair.second).GetELink() == TempELink))
1862  {
1863  AllRoutes->RemoveRouteElement(11, TempH, TempV, TempELink);
1864  }
1865  }
1866  if(AllRoutes->GetRouteTypeAndGraphics(3, MidElement, MidEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute)
1867  // Trains may not be in a route
1868  {
1869  int TempH = Track->TrackElementAt(216, MidElement).HLoc;
1870  int TempV = Track->TrackElementAt(217, MidElement).VLoc;
1871  int TempELink = Track->TrackElementAt(218, MidElement).Link[MidEntryPos];
1872  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1873  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(11, TempH, TempV, SecondPair);
1874  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(145, FirstPair.first).GetFixedPrefDirElementAt(155,
1875  FirstPair.second).GetELink() == TempELink))
1876  {
1877  AllRoutes->RemoveRouteElement(12, TempH, TempV, TempELink);
1878  }
1879  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(146, SecondPair.first).GetFixedPrefDirElementAt(156,
1880  SecondPair.second).GetELink() == TempELink))
1881  {
1882  AllRoutes->RemoveRouteElement(13, TempH, TempV, TempELink);
1883  }
1884  }
1885  if(AllRoutes->GetRouteTypeAndGraphics(4, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute)
1886  // Trains may not be in a route
1887  {
1888  int TempH = Track->TrackElementAt(219, LagElement).HLoc;
1889  int TempV = Track->TrackElementAt(220, LagElement).VLoc;
1890  int TempELink = Track->TrackElementAt(221, LagElement).Link[LagEntryPos];
1891  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1892  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(12, TempH, TempV, SecondPair);
1893  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(147, FirstPair.first).GetFixedPrefDirElementAt(157,
1894  FirstPair.second).GetELink() == TempELink))
1895  {
1896  AllRoutes->RemoveRouteElement(14, TempH, TempV, TempELink);
1897  }
1898  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(148, SecondPair.first).GetFixedPrefDirElementAt(158,
1899  SecondPair.second).GetELink() == TempELink))
1900  {
1901  AllRoutes->RemoveRouteElement(15, TempH, TempV, TempELink);
1902  }
1903  AllRoutes->CheckMapAndRoutes(8); // test
1904  }
1905  if(LagElement > -1)
1906  // not entering at a continuation so can deal with train leaving the lag element
1907  {
1909  // amended below so route elements removed for the complete train (for NotAutoSigsRoutes), so train never standing on a route once it
1910  // starts moving, covers for eliminating route when train reaches buffers, and prevents odd route segments when route extended while
1911  // straddling 3 elements (formerly the last segment was replotted as a route & stayed plotted
1912 
1913  TPrefDirElement PrefDirElement;
1914  // plot locked route marker for any element if appropriate (i.e. if a locked AutoSigs route) but only when train leaves element completely
1915  // as this is a 16x16 graphic
1917  {
1919  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
1920  }
1921  if(ContinuationExit(2, LagElement, LagExitPos)) // true if Element is a continuation and Exitpos is the continuation end
1922  {
1923  int RouteNumber;
1924  TrainGone = true;
1925  // flag to indicate train to be deleted - outside this function
1927  {
1928  TTrainController::TContinuationAutoSigEntry ContinuationAutoSigEntry;
1929  ContinuationAutoSigEntry.RouteNumber = RouteNumber;
1930  // calc distance from & inc last signal to exit
1931  int LastElement = LagElement, LastExitPos = LagExitPos, CumDistance = 0;
1932  int NewLastElement = 0, NewLastExitPos = 0;
1933  // need above because can't change LastElement & LastExitPos until both new values obtained
1934  // while((Track->TrackElementAt(684, LastElement).Config[LastExitPos] != Signal) && (CumDistance < 1200)) as was
1935  while((Track->TrackElementAt(913, LastElement).Config[LastExitPos] != Signal) && (CumDistance < 1200) && (Track->TrackElementAt(897,
1936  LastElement).TrackType != Points))
1937  // extra condition above added because of Moric1998's error (see email of 24/03/2016), where had an autosigs route across points, and another continuation on track not occupied by route so
1938  // failed when found a new element = -1 when tried to cross the continuation. Note this routine can only deal with non points as it uses GetNonPointsOppositeLinkPos
1939  // leave CumDistance as it was in these circumstances.
1940  {
1941  if(LastExitPos < 2)
1942  {
1943  CumDistance += Track->TrackElementAt(685, LastElement).Length01;
1944  }
1945  else
1946  {
1947  CumDistance += Track->TrackElementAt(686, LastElement).Length23;
1948  }
1949  NewLastElement = Track->TrackElementAt(687, LastElement).Conn[Track->GetNonPointsOppositeLinkPos(LastExitPos)];
1950  if(NewLastElement == -1)
1951  // this will catch buffers or any other connection failure
1952  {
1953  break; //throw Exception("Error, Connection = -1 in Continuation loop in UpdateTrain"); //dropped at v2.15.0 because of Brent Mackie's error file of
1954  } //10/02/23, had two continuations linked with no signal between
1955  NewLastExitPos = Track->TrackElementAt(688, LastElement).ConnLinkPos[Track->GetNonPointsOppositeLinkPos(LastExitPos)]; //so when train exited this routine tracked
1956  if(NewLastExitPos == -1) //back to the entry continuation which had no further connection - doesn't need to be an error at all!
1957  {
1958  break; //throw Exception("Error, ConnLinkPos = -1 in Continuation loop in UpdateTrain"); //dropped at v2.15.0 because of Brent Mackie's error file of 10/02/23 , see above
1959  }
1960  LastElement = NewLastElement;
1961  LastExitPos = NewLastExitPos;
1962  }
1963  // if at signal add this in too (may not be signal if 'break;' encountered but doesn't matter)
1964  if(CumDistance < 1200)
1965  {
1966  CumDistance += Track->TrackElementAt(689, LastElement).Length01; // only need 01 for signal
1967  }
1968  // now have distance including the signal, if >=1200m use 100m (for a signal immediately after the continuation)
1969  // else use 1200m - CumDistance
1970  int FirstDistance = 0;
1971  if(CumDistance >= 1200)
1972  {
1973  FirstDistance = 100;
1974  }
1975  else
1976  {
1977  FirstDistance = 1200 - CumDistance;
1978  }
1979  if(FirstDistance < 100)
1980  {
1981  FirstDistance = 100; // don't allow < 100
1982  }
1983  // can now calc the time delays in seconds - FirstDelay, SecondDelay & ThirdDelay, these are doubles
1984  // BUT - first check whether ExitSpeedFull is very low (Mark had divide by zero error with zero exit speed using v2.4.0)
1985  if(ExitSpeedFull > 20.0)
1986  {
1987  ContinuationAutoSigEntry.FirstDelay = 3.6 * double(FirstDistance) / ExitSpeedFull;
1988  // speed in km/h & distance in m so mult by 3.6 to bring to secs
1989  ContinuationAutoSigEntry.SecondDelay = ContinuationAutoSigEntry.FirstDelay + 4320.0 / ExitSpeedFull;
1990  // 4320.0 = 3.6 * 1200, .0 to make it a double
1991  ContinuationAutoSigEntry.ThirdDelay = ContinuationAutoSigEntry.SecondDelay + 4320.0 / ExitSpeedFull;
1992  }
1993  else
1994  {
1995  ContinuationAutoSigEntry.FirstDelay = 60.0; // 60 secs between each action
1996  ContinuationAutoSigEntry.SecondDelay = 120.0;
1997  ContinuationAutoSigEntry.ThirdDelay = 180.0;
1998  }
1999  ContinuationAutoSigEntry.AccessNumber = 0;
2000  ContinuationAutoSigEntry.PassoutTime = TrainController->TTClockTime;
2002  {
2004  for(VectorIT = TrainController->ContinuationAutoSigVector.begin(); VectorIT != TrainController->ContinuationAutoSigVector.end();
2005  VectorIT++)
2006  {
2007  if(VectorIT->RouteNumber == RouteNumber)
2008  {
2009  // another train has passed out of same route so erase earlier entry
2010  TrainController->ContinuationAutoSigVector.erase(VectorIT);
2011  break;
2012  }
2013  }
2014  }
2015  TrainController->ContinuationAutoSigVector.push_back(ContinuationAutoSigEntry);
2016  }
2018  // need to plot this as returning early so will miss the later plot (not a bridge so don't need PlotAlternativeTrackRouteGraphic)
2019  Display->Update();
2020  // need to keep this since Update() not called for PlotSmallOutput as too slow
2021  Utilities->CallLogPop(659);
2022  return;
2023  }
2024  // above covers for exiting at continuation, need XLinkPos check to exclude entering at a continuation
2025  if(LeadElement > -1)
2026  {
2027  TTrackElement &TE = Track->TrackElementAt(224, LeadElement); //added at v2.13.0 for brevity
2028  if(TE.Config[LeadExitPos] == Signal)
2029  // changed to lead so reset early
2030  {
2031  LastSigPassedFailed = false; //used to cancel route elements up to next signal for autosigs route
2032  TE.Attribute = 0; // red
2033  int RouteNumber; //only used for autosigs routes
2034  //add chance to fail when train passes a signal
2035  if((random(Utilities->SignalChangeEventsPerFailure) == 0) && !TE.Failed && (Utilities->FailureMode != FNil) &&
2036  (TrainMode == Timetable) && !TE.CallingOnSet) //can't fail twice, calling on signal can't fail
2037  {
2039  IFE.TVPos = LeadElement;
2040  TE.Failed = true;
2041  Display->WarningLog(19, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": Signal failed at " + TE.ElementID);
2042  PerfLogForm->PerformanceLog(42, Utilities->Format96HHMMSS(TrainController->TTClockTime) + " WARNING: Signal failed at " + TE.ElementID);
2043  TrainController->StopTTClockMessage(129, "Signal at " + TE.ElementID +
2044  " failed when changing aspect.\nTrains can only pass under signaller control.");
2045  AllRoutes->RebuildRailwayFlag = true; //force ClearandRebuildRailway at next clock tick
2046  LastSigPassedFailed = true;
2047  //set repair time, random value in minutes between 10 and 179
2048  double FailureMinutes = double(random(Utilities->MaxRandomRepairTime) + Utilities->FixedMinRepairTime); //between 10 and 179 minutes at random
2049  TDateTime RepairTime = TrainController->TTClockTime + TDateTime(FailureMinutes / 1440);
2050  IFE.RepairTime = RepairTime;
2052  Track->FailedSignalsVector.push_back(IFE); //rearwards signals will be set when LagElement leaves signal
2053  }
2054  TE.CallingOnSet = false;
2055  // don't plot if zoomed out
2056  if(!Display->ZoomOutFlag)
2057  {
2059  }
2060  // covers signal resetting in same direction
2061  }
2062  }
2064  {
2065  AllRoutes->RebuildRailwayFlag = true; //added at v2.13.0 to replot signal after train left in case it had failed
2066  if(AllRoutes->GetRouteTypeAndGraphics(5, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::AutoSigsRoute)
2067  {
2068  Display->PlotOutput(23, Track->TrackElementAt(227, LagElement).HLoc * 16, Track->TrackElementAt(228, LagElement).VLoc * 16, EXGraphicPtr);
2069  Display->PlotOutput(24, Track->TrackElementAt(229, LagElement).HLoc * 16, Track->TrackElementAt(230, LagElement).VLoc * 16, EntryDirectionGraphicPtr);
2070  TPrefDirElement PrefDirElement;
2071  // plot locked route marker for same side signal if appropriate (may be covered above but leave in), but only when train leaves element completely as this is a 16x16 graphic
2073  {
2075  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
2076  }
2078  LockedVectorNumber)))
2079  {
2081  }
2082  }
2083  }
2084  else if((LeadElement > -1) && (Track->TrackElementAt(233, LeadElement).TrackType == SignalPost))
2085  {
2086  Track->TrackElementAt(234, LeadElement).Attribute = 0; // red
2088  // don't plot if zoomed out
2089  if(!Display->ZoomOutFlag)
2090  {
2092  }
2093  // covers signal passed in opposite direction - replot as red, regardless of what it was before, though should already have been red
2094  }
2096  {
2097  if(AllRoutes->GetRouteTypeAndGraphics(6, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::AutoSigsRoute)
2098  {
2099  Display->PlotOutput(26, Track->TrackElementAt(236, LagElement).HLoc * 16, Track->TrackElementAt(237, LagElement).VLoc * 16, EXGraphicPtr);
2100  Display->PlotOutput(27, Track->TrackElementAt(238, LagElement).HLoc * 16, Track->TrackElementAt(239, LagElement).VLoc * 16, EntryDirectionGraphicPtr);
2101  // below added at v1.3.0 to reset signals if back out of an autosigs route under signaller control after changing direction, when new LeadElement not on route (if it had
2102  // been the route would have been ForceCancelled). Note that the signal is not facing the direction of travel else would have entered
2103  // "if(Track->TrackElementAt(, LagElement).Config[LagExitPos] == Signal)" above and wouldn't be here
2104  int RouteNumber;
2106  // already know it's an autosigsroute, this is just to get the RouteNumber
2107  // addition below at v1.3.2 - found that a signal that had reached double yellow in ContinuationAutoSigs was reset to red when a following train's lag element
2108  // moved off a signal in the normal course of events. It was caused when a train backed out of an autosigs route under signaller control after changing
2109  // direction (see DevHistory.txt). Hence check that the train is in signaller mode and that the train's lead element isn't on the same route before calling SetRouteSignals.
2110  int RouteNumber2;
2112  // already know it's an autosigsroute, this is just to get the RouteNumber
2113  if((TrainMode == Signaller) && (RouteNumber2 != RouteNumber))
2114  // note that if not in a route (as likely) then RouteNumber2 set to -1
2115  {
2116  AllRoutes->GetFixedRouteAt(217, RouteNumber).SetRouteSignals(10);
2117  // this was in the 1.3.0 addition but without the condition
2118  }
2119  // end of 1.3.2 addition
2120  // end of 1.3.0.addition
2121  }
2122  TPrefDirElement PrefDirElement;
2123  // plot locked route marker for opp side signal if appropriate (may be covered above but leave in), but only when train leaves element completely as this is a 16x16 graphic (OK - Straddle == LeadMidLag)
2125  {
2127  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
2128  }
2129  }
2130  }
2131  }
2132  // straddle ONLY changed here, check if 'LeadMid' first & if so ready for updating Elements
2133  if(Straddle == LeadMid) //about to move half onto next element
2134  {
2135  AllowedToPassRedSignal = false;
2136  // if had been allowed to pass then at this point it will move half onto signal so can be reset
2137  // if(LagElement > -1) ResetTrainElementID(LagElement, LagEntryPos);//train fully off old LagElement so can clear TrainOnElement flags - no, reset at earlier call when lag moves off element
2138  if(DerailPending)
2139  // set during last GetLeadElement, but only act on it when train fully on offending point
2140  // i.e. next time Straddle reaches LeadMid
2141  {
2142  Derailed = true;
2143  DerailPending = false;
2147  Utilities->CallLogPop(657);
2148  return;
2149  }
2156  Straddle = MidLag;
2157  // train now fully on the updated Lag & Mid, the front segment is going to move onto the new
2158  // LeadElement during this function (note that if stopped at signal then won't get this far)
2159  if(LeadElement > -1)
2160  {
2162  // i.e an exit continuation only
2163  // if don't exclude entry continuations then can't progress past it
2164  {
2165  LeadElement = -1;
2166  }
2167  else
2168  {
2169  GetLeadElement(0);
2170  // sets or resets DerailPending & StoppedAtSignal, and sets LeadElement values
2172  if(Stopped())
2173  {
2174  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
2175  {
2176  TrainHasFailed(6);
2177  }
2178  Utilities->CallLogPop(658);
2179  return; // i.e. don't move forward one step if next element is a red signal
2180  }
2181  }
2182  }
2183  }
2184  if(LagElement > -1)
2185  {
2186  // below are the actions required at both half moves for LagElement > -1
2188 
2189  // if was in locked route but has timed out when train leaves then plot the normal track graphic over the route graphic that is
2190  // still in BackgroundGraphic[3], if wasn't in a route then will just replot the same BackgroundGraphic
2191  // need to do this for each half element
2192 
2193  TPrefDirElement PrefDirElement;
2194  if(!(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(7, LagElement, LagExitPos, PrefDirElement, LockedVectorNumber)))
2195  {
2196  int RouteNumber; // holder for call below - not used
2198  {
2199  if(Utilities->clTransparent == TColor(0xFFFFFF))
2200  // change to black for a white background
2201  {
2203  // only applies for AutoSigs Route in case was locked & timed out
2204  }
2205  else
2206  // change to white for a dark background
2207  {
2209  // only applies for AutoSigs Route in case was locked & timed out
2210  }
2212  }
2213  }
2215  // above in case train just moving off a bridge & either alternative track in a route - need to keep its route colour,
2216  // or a train on the opposite track - needs to be replotted
2217  }
2218  // update all array values
2219  HOffset[3] = HOffset[2];
2220  HOffset[2] = HOffset[1];
2221  HOffset[1] = HOffset[0];
2222  VOffset[3] = VOffset[2];
2223  VOffset[2] = VOffset[1];
2224  VOffset[1] = VOffset[0];
2225  Graphics::TBitmap *TempPtr = BackgroundPtr[3];
2226 
2227  BackgroundPtr[3] = BackgroundPtr[2];
2228  BackgroundPtr[2] = BackgroundPtr[1];
2229  BackgroundPtr[1] = BackgroundPtr[0];
2230  BackgroundPtr[0] = TempPtr;
2231 
2232  // update headcode graphics depending on Lead entry value
2233  if(LeadElement > -1) // if Lead is -1 then stays as is
2234  {
2236  {
2237  for(int x = 0; x < 4; x++)
2238  {
2239  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
2240  }
2241  }
2242  else
2243  {
2244  for(int x = 0; x < 4; x++)
2245  {
2247  }
2248  }
2249  }
2250  if(TrainMode == Timetable)
2251  {
2253  }
2254  else
2255  {
2257  }
2259 
2260  // plot new seg [0] on Lead & [2] on Mid ([2] always on Mid)
2261  if(LeadElement > -1)
2262  {
2263  if(Straddle == MidLag)
2264  // just about to move half onto the new lead element
2265  {
2267  // pick up new background bitmap [0]
2269  int LeadElementTrainID = Track->TrackElementAt(244, LeadElement).TrainIDOnElement;
2270  if((LeadElementTrainID > -1) && (LeadElementTrainID != TrainID))
2271  // check if own ID for entry at continuation, else crashes into itself!
2272  {
2273  // OK if crossing on a bridge
2274  int OtherTrainEntryPos = TrainController->EntryPos(0, LeadElementTrainID, LeadElement);
2275  if(OtherTrainEntryPos == -1)
2276  {
2277  throw Exception("Error - OtherTrainEntryPos not set");
2278  }
2279  if((Track->TrackElementAt(246, LeadElement).TrackType != Bridge) || (LeadEntryPos == OtherTrainEntryPos) ||
2280  // LeadEntryPos for rear end crashes
2281  (LeadExitPos == OtherTrainEntryPos))
2282  // LeadExitPos for head-on crashes
2283  {
2285  Crashed = true; // only set if Straddle = MidLag
2286  CallingOnFlag = false;
2287  // in case was set, need to disable call on if call on button had been pressed
2288  }
2289  }
2290  else if(MidElement > -1) // will be -1 for continuation entries
2291  {
2292  // check if about to move onto a crossing diagonal that is occupied by another train, and if so crash
2293  int MidExitLinkNum = Track->TrackElementAt(889, MidElement).Link[MidExitPos];
2294  int MidHLoc = Track->TrackElementAt(890, MidElement).HLoc;
2295  int MidVLoc = Track->TrackElementAt(891, MidElement).VLoc;
2296  int OtherTrainID = -1;
2297  if((MidExitLinkNum == 1) || (MidExitLinkNum == 3) || (MidExitLinkNum == 7) || (MidExitLinkNum == 9))
2298  {
2299  if(Track->DiagonalFouledByTrain(0, MidHLoc, MidVLoc, MidExitLinkNum, OtherTrainID))
2300  {
2301  TrainCrashedInto = OtherTrainID;
2302  Crashed = true; // only set if Straddle = MidLag
2303  CallingOnFlag = false;
2304  // in case was set, need to disable call on if call on button had been pressed
2305  }
2306  }
2307  }
2308  }
2309  else
2310  {
2312  // pick up new background bitmap [0]
2314  }
2315  PlotElement[0] = LeadElement;
2317  PlotTrainGraphic(12, 0, Display);
2318  }
2319  if(MidElement > -1)
2320  {
2321  PlotElement[2] = MidElement;
2323  PlotTrainGraphic(1, 2, Display);
2324  }
2325  // plot the new positions for [1] & [3] graphics - [1] on Mid if Straddle = MidLag, on Lead if Straddle = LeadMidLag
2326  // [3] on Lag if Straddle = MidLag, on Mid if Straddle = LeadMidLag
2327  if(Straddle == MidLag)
2328  {
2329  if(MidElement > -1)
2330  {
2331  PlotElement[1] = MidElement;
2333  PlotTrainGraphic(2, 1, Display);
2334  }
2335  if(LagElement > -1)
2336  {
2337  PlotElement[3] = LagElement;
2339  PlotTrainGraphic(3, 3, Display);
2340  }
2341  }
2342  else // Straddle == LeadMidLag
2343  {
2344  if(LeadElement > -1)
2345  {
2346  PlotElement[1] = LeadElement;
2348  PlotTrainGraphic(4, 1, Display);
2349  }
2350  if(MidElement > -1)
2351  {
2352  PlotElement[3] = MidElement;
2354  PlotTrainGraphic(5, 3, Display);
2355  }
2356  }
2357  if(Crashed)
2358  // only reach here if crash into another train, if crash into buffers or an LC then return earlier at the if(Stopped()) test
2359  {
2364  // in case was set, need to disable call on if call on button had been pressed
2371  Straddle = LeadMidLag;
2372  // was MidLag but plotted as LeadMidLag so change Straddle accordingly
2373  Display->Update();
2374  // resurrected when Update() dropped from PlotOutput etc
2375  Utilities->CallLogPop(660);
2376  return;
2377  }
2378  // deal here with station stops & pass times after all replotting done but before Straddle changed
2379  if(TrainMode == Timetable)
2380  {
2381  if(Straddle == LeadMidLag)
2382  {
2383  if((LeadElement > -1) && (MidElement > -1) && !TimetableFinished)
2384  {
2385  // NameInTimetableBeforeCDT returns the number by which the train ActionVectorEntryPtr needs to be incremented
2386  // to point to the location arrival entry - before a change of direction
2387  AnsiString LocName = Track->TrackElementAt(249, LeadElement).ActiveTrackElementName;
2388  bool StopRequired = false;
2389  int TTVPos = NameInTimetableBeforeCDT(1, LocName, StopRequired); //excludes continuations
2390  if(TTVPos > -1) // -1 if can't find it or if name is ""
2391  {
2392  // check if at buffers (no, dropped buffer check to allow to crash into buffers) or a through station stop,
2393  // or a station where next element contains a train or a stop signal, if so
2394  // stop now, note that for 2nd check, if next element is a bridge then will have stopped by now so no need
2395  // to test the actual track the train is on since it can't be a platform
2396  TTrackElement LeadTrackElement = Track->TrackElementAt(258, LeadElement);
2397  TTrackElement NextTrackElement; // default for now
2398  bool TrainAtStopLinkPos1 = (LeadTrackElement.StationEntryStopLinkPos1 == LeadEntryPos);
2399  bool TrainAtStopLinkPos2 = (LeadTrackElement.StationEntryStopLinkPos2 == LeadEntryPos);
2400  bool TrainAtStopLinkPos3 = (LeadTrackElement.StationEntryStopLinkPos3 == LeadEntryPos);
2401  bool TrainAtStopLinkPos4 = (LeadTrackElement.StationEntryStopLinkPos4 == LeadEntryPos);
2402  bool ForwardConnection = (LeadTrackElement.Conn[LeadExitPos] > -1);
2403  int NextElementEntryPos = -1;
2404  int NextElementExitPos = -1;
2405  bool TrainOnNextElement = false;
2406  bool StopSignalAtNextElement = false;
2407  if(ForwardConnection)
2408  // if no forward connection can't derive anything from it without errors
2409  {
2410  NextTrackElement = Track->TrackElementAt(262, LeadTrackElement.Conn[LeadExitPos]);
2411  NextElementEntryPos = LeadTrackElement.ConnLinkPos[LeadExitPos];
2412  NextElementExitPos = Track->GetNonPointsOppositeLinkPos(NextElementEntryPos);
2413  // this is only for signals so no need to worry about points ambiguity
2414  TrainOnNextElement = (NextTrackElement.TrainIDOnElement > -1);
2415  StopSignalAtNextElement = ((NextTrackElement.Config[NextElementExitPos] == Signal) && (NextTrackElement.Attribute == 0));
2416  }
2417  if(TrainAtStopLinkPos1 || TrainAtStopLinkPos2 || TrainAtStopLinkPos3 || TrainAtStopLinkPos4 || (ForwardConnection && (TrainOnNextElement || StopSignalAtNextElement)))
2418  {
2419  if(TTVPos > 0)
2420  {
2422  ActionVectorEntryPtr += TTVPos;
2423  }
2424  if(StopRequired) //this is where ActualArrivalTime & ArrivalMinDwellTime are set for both TimeLoc Arrivals and TimeTimeLocs
2425  { //note that the above values are retained for all finish service/start new service events
2426  StoppedAtLocation = true;
2427  StoppedAtSignal = false;
2428  // may have been set earlier at line 925 so need to reset as
2429  // StoppedAtLocation takes precedence and don't want both set at same time or have flashing graphic
2430  // in zoom out mode
2431  if(!TrainFailed)
2432  {
2434  // pale green
2435  }
2436  ActualArrivalTime = TrainController->TTClockTime; //added at v2.13.0
2437  ArrivalMinDwellTime = ActionVectorEntryPtr->MinDwellTime; //in order to retrieve MinDwellTime when reach departure
2438  if(ArrivalMinDwellTime > 30.1) //i.e. not the default value
2439  {
2441  }
2443  {
2444  TimeTimeLocArrived = true;
2445  // used in case of later signaller control, when need to know
2446  // whether had arrived or not, to avoid sending the arrival
2447  // message twice, see TInterface::TimetableControl1Click
2448  }
2450  }
2451  else
2452  {
2454  }
2456  {
2458  }
2459  // don't alter ActionVectorEntryPtr if at a TimeTimeLoc (& can't be anything else other than TimeLoc or PassTime after calling NameInTimetableBeforeCDT successfully)
2461  }
2462  }
2463  }
2464  }
2465  }
2466  if(Straddle == MidLag)
2467  {
2468  Straddle = LeadMidLag;
2469  FirstHalfMove = false;
2470  }
2471  else if(Straddle == LeadMidLag)
2472  {
2473  Straddle = LeadMid;
2474  FirstHalfMove = true;
2475  }
2476  else if(Straddle == LeadMid)
2477  {
2478  throw Exception("Error, Straddle shouldn't be LeadMid prior to resetting at exit from UpdateTrain");
2479  }
2480  if(TrainFailurePending) // ok, moving but PlotElements set above
2481  {
2482  TrainHasFailed(7);
2483  }
2485  Display->Update();
2486  // need to keep this since Update() not called for PlotSmallOutput as too slow
2487  Utilities->CallLogPop(661);
2488 }
2489 
2490 // ----------------------------------------------------------------------------
2491 
2493 {
2494  if((!Display->ZoomOutFlag) && (TrainDataEntryPtr->ServiceReference.Length() > 4))
2495  {
2497  {
2499  }
2500  if(Plotted && (LeadElement > -1) && Utilities->ShowLongServRefsFlag) //if not plotted yet, exiting at continuation, or show flag false then ignore
2501  {
2502  EnterLongServRefAsName(0, Disp);
2503  }
2504  }
2505 }
2506 
2507 // ----------------------------------------------------------------------------
2508 
2509 void TTrain::EnterLongServRefAsName(int Caller, TDisplay *Disp) //added at v2.22.0 to display long serv refs
2510 { //colours clB1G0R0 for white bgnd, clB3G5R5 for dark bgnds
2511 
2512 /* There are several parts to this function as follows:
2513 StaticFeaturesDisplay & StaticfeaturesScreen are created in the TInterface constructor (and destroyed in the destructor).
2514 The display is a Display object like Display and HiddenDisplay, and has HiddenDisplay assigned to it almost at the end of Clearand...
2515 just before the trains are plotted. It is used to remove the text of the long serv. ref. prior to replotting it after the train has moved.
2516 The colours for the text and the font are set in the TTrainController constructor according to the background colour.
2517 There are two small bitmaps LongServRefNameBitmap (54 x 13 pixels) and LongServRefWorkingBitmap (54 x 10 pixels), the former to hold the
2518 long serv. ref. text on a transparent background, and the latter to hold the bitmap to be overlaid on the main display both for plotting
2519 and removal. The former is taller because when text is plotted there are 3 pixels at the top above the text that aren't used, so
2520 these are omitted for overlaying on the main display.
2521 StaticFeaturesDisplay is resized whenever the form is resized, and is cleared and filled with the transparent colour whenever OperMode or
2522 RestartOperMode selected.
2523 This function (EnterLongServRefAsName) calculates the text position (HPos & VPos, saved in LongServRefTextH & LongServRefTextV) for removal, which may be a long time
2524 after plotting. DisplayOffsetH & V are static values so apply to all three displays, and are used to ensure the correct text removal
2525 graphic is picked up from the StaticFeaturesScreen - picking anything up outside this area is blank.
2526 The text of the long serv. ref. uses unique colours (for white & dark backgrounds) so these can be identified when plotting over the main
2527 display graphic using the scanline function - LongServRefNameBitmap is examined starting 3 pixels down and where a pixel uses the relevant
2528 text colour-number (using the websafe colour palette) it is transferred onto LongServRefWorkingBitmap so the name overlies anything already there,
2529 then LongServRefWorkingBitmap drawn onto the main display at the correct position (it uses draw instead of CopyRect as that respects
2530 transparency. After plotting LongServRefWorkingBitmap is loaded from the same position in StaticFeaturesDisplay, then where LongServRefNameBitmap
2531 contains a text coloured pixel a near-transparent colour is plotted onto LongServRefWorkingBitmap in its place. Where any other colour including
2532 transparent appears it is plotted without change. A near-transparent colour is used so it won't show against the existing background, the transparent
2533 colour can't be used as it would leave the text in place.
2534 */
2535 
2536  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",EnterLongServRefAsName");
2537  if(LongServRefEnteredFlag) //if already present then return
2538  {
2539  Utilities->CallLogPop(2724);
2540  return;
2541  }
2542  int VPos, HPos, TrainHLocLead, TrainHLocMid, TrainHLocLag, TrainVLocLead, TrainVLocMid, TrainVLocLag;
2543  TTrackElement TrainLead, TrainMid, TrainLag;
2544  bool ReturnEarly = false;
2545 
2546  //set all H & VPos's
2547  if(LeadElement > -1)
2548  {
2549  TrainLead = Track->TrackElementAt(1686, LeadElement);
2550  }
2551  else
2552  {
2553  ReturnEarly = true;
2554  }
2555  if(MidElement > -1)
2556  {
2557  TrainMid = Track->TrackElementAt(1687, MidElement);
2558  }
2559  else
2560  {
2561  ReturnEarly = true;
2562  }
2563  if(LagElement > -1)
2564  {
2565  TrainLag = Track->TrackElementAt(1688, LagElement);
2566  }
2567  else if(Straddle == LeadMidLag)
2568  {
2569  ReturnEarly = true;
2570  }
2571  if(ReturnEarly)
2572  {
2573  Utilities->CallLogPop(2725);
2574  return;
2575  }
2576 
2577  int LeftHAndOffset, TopVAndOffset;
2578 //get leftmost and topmost train graphic elements
2579 
2580  if(Straddle == LeadMid)
2581  {
2582  LeftHAndOffset = (TrainLead.HLoc * 16) + HOffset[0];
2583  if(((TrainLead.HLoc * 16) + HOffset[1]) < LeftHAndOffset)
2584  {
2585  LeftHAndOffset = (TrainLead.HLoc * 16) + HOffset[1];
2586  }
2587  if(((TrainMid.HLoc * 16) + HOffset[2]) < LeftHAndOffset)
2588  {
2589  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[2];
2590  }
2591  if(((TrainMid.HLoc * 16) + HOffset[3]) < LeftHAndOffset)
2592  {
2593  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[3];
2594  }
2595  TopVAndOffset = (TrainLead.VLoc * 16) + VOffset[0];
2596  if(((TrainLead.VLoc * 16) + VOffset[1]) < TopVAndOffset)
2597  {
2598  TopVAndOffset = (TrainLead.VLoc * 16) + VOffset[1];
2599  }
2600  if(((TrainMid.VLoc * 16) + VOffset[2]) < TopVAndOffset)
2601  {
2602  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[2];
2603  }
2604  if(((TrainMid.VLoc * 16) + VOffset[3]) < TopVAndOffset)
2605  {
2606  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[3];
2607  }
2608  HPos = LeftHAndOffset;
2609  VPos = TopVAndOffset - 10; // - 10 so plots 10 pixels above the train
2610  }
2611  else if(Straddle == LeadMidLag)
2612  {
2613  LeftHAndOffset = (TrainLead.HLoc * 16) + HOffset[0];
2614  if(((TrainMid.HLoc * 16) + HOffset[1]) < LeftHAndOffset)
2615  {
2616  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[1];
2617  }
2618  if(((TrainMid.HLoc * 16) + HOffset[2]) < LeftHAndOffset)
2619  {
2620  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[2];
2621  }
2622  if(((TrainLag.HLoc * 16) + HOffset[3]) < LeftHAndOffset)
2623  {
2624  LeftHAndOffset = (TrainLag.HLoc * 16) + HOffset[3];
2625  }
2626  TopVAndOffset = (TrainLead.VLoc * 16) + VOffset[0];
2627  if(((TrainMid.VLoc * 16) + VOffset[1]) < TopVAndOffset)
2628  {
2629  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[1];
2630  }
2631  if(((TrainMid.VLoc * 16) + VOffset[2]) < TopVAndOffset)
2632  {
2633  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[2];
2634  }
2635  if(((TrainLag.VLoc * 16) + VOffset[3]) < TopVAndOffset)
2636  {
2637  TopVAndOffset = (TrainLag.VLoc * 16) + VOffset[3];
2638  }
2639  HPos = LeftHAndOffset;
2640  VPos = TopVAndOffset - 10; // - 10 so plots 10 pixels above the train
2641  }
2642  else //shouldn't be anything else
2643  {
2644  Utilities->CallLogPop(2726);
2645  return;
2646  }
2647 
2648  LongServRefTextH = HPos; //have to store these so text can be erased using absolute location
2649  LongServRefTextV = VPos;
2650 
2651  //write service reference to bitmap after clearing it, can't do it in constructor as service ref not set at that stage
2652  LongServRefNameBitmap->Transparent = true;
2653  LongServRefNameBitmap->TransparentColor = Utilities->clTransparent;
2654  LongServRefNameBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2655  LongServRefNameBitmap->Canvas->Brush->Color = Utilities->clTransparent;
2656  LongServRefNameBitmap->Canvas->FillRect(TRect(0,0,54,13)); //fill it with transparent colour
2657  LongServRefNameBitmap->Canvas->Font->Assign(TrainController->LongServRefFont); //assign all font properties
2658  LongServRefNameBitmap->Canvas->TextOut(2, 0, TrainDataEntryPtr->ServiceReference);
2659 
2660 //Display->GetImage()->Picture->Bitmap->Canvas->CopyRect(TRect(0,0,54,13), LongServRefNameBitmap->Canvas, TRect(0,0,54,13));
2661 
2662  //copy MainScreen background segment but clear it first
2663  LongServRefWorkingBitmap->Transparent = true;
2664  LongServRefWorkingBitmap->TransparentColor = Utilities->clTransparent;
2665  LongServRefWorkingBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2666  LongServRefWorkingBitmap->Canvas->Brush->Color = Utilities->clTransparent;
2667  LongServRefWorkingBitmap->Canvas->FillRect(TRect(0,0,54,10)); //fill it with transparent colour
2668 
2669  LongServRefWorkingBitmap->Canvas->CopyRect(TRect(0,0,54,10), Disp->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2671 
2672 //set up ImageLongServRefBitmap ready for receiving name & on white background
2673  ImageLongServRefBitmap->Transparent = true;
2674  ImageLongServRefBitmap->TransparentColor = clB5G5R5; //white
2675  ImageLongServRefBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2676  ImageLongServRefBitmap->Canvas->Brush->Color = clB5G5R5; //white
2677  ImageLongServRefBitmap->Canvas->FillRect(TRect(0,0,54,10)); //fill it with white
2678 
2679  Byte *SLPtrIn; // pointer to the ScanLine values in LongServRefNameBitmap
2680  Byte *SLPtrOut; // pointer to the ScanLine values in LongServRefWorkingBitmap
2681  Byte *SLPtrImage; // pointer to the ScanLine values in ImageLongServRefBitmap
2682 
2683  for(int x = 3; x < 13; x++)
2684  {
2685  SLPtrIn = reinterpret_cast<Byte*>(LongServRefNameBitmap->ScanLine[x]);
2686  SLPtrOut = reinterpret_cast<Byte*>(LongServRefWorkingBitmap->ScanLine[x - 3]);
2687  SLPtrImage = reinterpret_cast<Byte*>(ImageLongServRefBitmap->ScanLine[x - 3]);
2688  for(int y = 0; y < 54; y++)
2689  {
2690  if(SLPtrIn[y] == TrainController->LongServRefFontColNumber)
2691  {
2692  SLPtrOut[y] = SLPtrIn[y];
2693  SLPtrImage[y] = 0x01; //clB1G0R0
2694  } //else do nothing
2695  }
2696  }
2697 
2698 //ImageLongServRefBitmap now set up ready to write to image if required
2699 
2700 //copy back onto MainScreen - use Draw to retain transparent pixels CopyRect doesn't accept transparent pixels
2702 
2703 //from here building a new LongServRefWorkingBitmap ready for the next removal
2704  LongServRefWorkingBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2705  LongServRefWorkingBitmap->Canvas->Brush->Color = Utilities->clTransparent;
2706  LongServRefWorkingBitmap->Canvas->FillRect(TRect(0,0,54,10)); //fill it with transparent colour
2707  LongServRefWorkingBitmap->Canvas->CopyRect(TRect(0,0,54,10), StaticFeaturesDisplay->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2709  LongServRefTextV - (Display->DisplayOffsetV * 16) + 10));
2710 //now just get the background pixels where overlap with text
2711 
2712 //Disp->GetImage()->Canvas->CopyRect(TRect(0,0,200,200), StaticFeaturesDisplay->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2713 // LongServRefTextV - (Display->DisplayOffsetV * 16), LongServRefTextH - (Display->DisplayOffsetH * 16) + 200,
2714 // LongServRefTextV - (Display->DisplayOffsetV * 16) + 200));
2715 
2716  byte *SLPtrText;
2717  for(int x = 3; x < 13; x++)
2718  {
2719  SLPtrText = reinterpret_cast<Byte*>(LongServRefNameBitmap->ScanLine[x]); //this is 13 pixels deep
2720  SLPtrOut = reinterpret_cast<Byte*>(LongServRefWorkingBitmap->ScanLine[x - 3]); //this is 10 pixels deep
2721  for(int y = 0; y < 54; y++)
2722  {
2723  if(SLPtrText[y] == TrainController->LongServRefFontColNumber)
2724  {
2725  if(SLPtrOut[y] == TrainController->BgndColNumber)
2726  {
2728  } //else leave pixel as it is
2729  } //else leave pixel as it is
2730  }
2731  }
2732 
2733 //Disp->GetImage()->Canvas->CopyRect(TRect(0,210, 54,220), LongServRefWorkingBitmap->Canvas, TRect(0,0,54,10));
2734 
2735  Disp->Update();
2736  LongServRefEnteredFlag = true;
2737  Utilities->CallLogPop(2727);
2738 }
2739 
2740 // ----------------------------------------------------------------------------
2741 
2742 void TTrain::RemoveLongServRef(int Caller, AnsiString NameText, TDisplay *Disp) //added at v2.22.0 to remove long serv refs
2743 {
2744  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RemoveLongServRef," + NameText);
2745  if(!LongServRefEnteredFlag) //if already absent then return
2746  {
2747  Utilities->CallLogPop(2728);
2748  return;
2749  }
2750 
2751 //copy back onto MainScreen, but first avoid overwriting any existing leading train colours (grey [clB4G4R4], red [clB0G0R5], or blue [clB5G0R0])
2752 //Pick up the relevant section of the main display onto a new bitmap
2753  Graphics::TBitmap *TempBitmap = new Graphics::TBitmap;
2754  TempBitmap->PixelFormat = pf8bit;
2755  TempBitmap->Height = 10; //shorter than name bitmap as top 3 pixels not occupied by text
2756  TempBitmap->Width = 54;
2757  RailGraphics->SetWebSafePalette(64, TempBitmap);
2758  TempBitmap->Canvas->CopyRect(TRect(0,0,54,10), Disp->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2760 
2761  Byte *SLPtrIn; // pointer to the ScanLine values in TempBitmap
2762  Byte *SLPtrOut; // pointer to the ScanLine values in LongServRefWorkingBitmap
2763  for(int x = 0; x < 10; x++)
2764  {
2765  SLPtrIn = reinterpret_cast<Byte*>(TempBitmap->ScanLine[x]);
2766  SLPtrOut = reinterpret_cast<Byte*>(LongServRefWorkingBitmap->ScanLine[x]);
2767  for(int y = 0; y < 54; y++)
2768  {
2769  if(SLPtrIn[y] == 0xac) //this is clB4G4R4 - grey
2770  {
2771  SLPtrOut[y] = TrainController->BgndColNumber;
2772  }
2773  else if(SLPtrIn[y] == 0xb4) //this is clB0G0R5 - red for leading character
2774  {
2775  SLPtrOut[y] = TrainController->BgndColNumber;
2776  }
2777  else if(SLPtrIn[y] == 0x05) //this is clB5G0R0 - blue for signaller control leading character
2778  {
2779  SLPtrOut[y] = TrainController->BgndColNumber;
2780  }
2781  }
2782  }
2783 
2785  Disp->Update();
2786  LongServRefEnteredFlag = false;
2787  delete TempBitmap;
2788  Utilities->CallLogPop(2729);
2789 }
2790 
2791 // ----------------------------------------------------------------------------
2792 
2793 Graphics::TBitmap *TTrain::SetOneGraphicCode(char CodeChar)
2794 {
2795  switch(CodeChar)
2796  {
2797  case '0':
2798  return(RailGraphics->Code0);
2799 
2800  case '1':
2801  return(RailGraphics->Code1);
2802 
2803  case '2':
2804  return(RailGraphics->Code2);
2805 
2806  case '3':
2807  return(RailGraphics->Code3);
2808 
2809  case '4':
2810  return(RailGraphics->Code4);
2811 
2812  case '5':
2813  return(RailGraphics->Code5);
2814 
2815  case '6':
2816  return(RailGraphics->Code6);
2817 
2818  case '7':
2819  return(RailGraphics->Code7);
2820 
2821  case '8':
2822  return(RailGraphics->Code8);
2823 
2824  case '9':
2825  return(RailGraphics->Code9);
2826 
2827  case 'A':
2828  return(RailGraphics->CodeA);
2829 
2830  case 'B':
2831  return(RailGraphics->CodeB);
2832 
2833  case 'C':
2834  return(RailGraphics->CodeC);
2835 
2836  case 'D':
2837  return(RailGraphics->CodeD);
2838 
2839  case 'E':
2840  return(RailGraphics->CodeE);
2841 
2842  case 'F':
2843  return(RailGraphics->CodeF);
2844 
2845  case 'G':
2846  return(RailGraphics->CodeG);
2847 
2848  case 'H':
2849  return(RailGraphics->CodeH);
2850 
2851  case 'I':
2852  return(RailGraphics->CodeI);
2853 
2854  case 'J':
2855  return(RailGraphics->CodeJ);
2856 
2857  case 'K':
2858  return(RailGraphics->CodeK);
2859 
2860  case 'L':
2861  return(RailGraphics->CodeL);
2862 
2863  case 'M':
2864  return(RailGraphics->CodeM);
2865 
2866  case 'N':
2867  return(RailGraphics->CodeN);
2868 
2869  case 'O':
2870  return(RailGraphics->CodeO);
2871 
2872  case 'P':
2873  return(RailGraphics->CodeP);
2874 
2875  case 'Q':
2876  return(RailGraphics->CodeQ);
2877 
2878  case 'R':
2879  return(RailGraphics->CodeR);
2880 
2881  case 'S':
2882  return(RailGraphics->CodeS);
2883 
2884  case 'T':
2885  return(RailGraphics->CodeT);
2886 
2887  case 'U':
2888  return(RailGraphics->CodeU);
2889 
2890  case 'V':
2891  return(RailGraphics->CodeV);
2892 
2893  case 'W':
2894  return(RailGraphics->CodeW);
2895 
2896  case 'X':
2897  return(RailGraphics->CodeX);
2898 
2899  case 'Y':
2900  return(RailGraphics->CodeY);
2901 
2902  case 'Z':
2903  return(RailGraphics->CodeZ);
2904 
2905  case 'a':
2906  return(RailGraphics->Code_a);
2907 
2908  case 'b':
2909  return(RailGraphics->Code_b);
2910 
2911  case 'c':
2912  return(RailGraphics->Code_c);
2913 
2914  case 'd':
2915  return(RailGraphics->Code_d);
2916 
2917  case 'e':
2918  return(RailGraphics->Code_e);
2919 
2920  case 'f':
2921  return(RailGraphics->Code_f);
2922 
2923  case 'g':
2924  return(RailGraphics->Code_g);
2925 
2926  case 'h':
2927  return(RailGraphics->Code_h);
2928 
2929  case 'i':
2930  return(RailGraphics->Code_i);
2931 
2932  case 'j':
2933  return(RailGraphics->Code_j);
2934 
2935  case 'k':
2936  return(RailGraphics->Code_k);
2937 
2938  case 'l':
2939  return(RailGraphics->Code_l);
2940 
2941  case 'm':
2942  return(RailGraphics->Code_m);
2943 
2944  case 'n':
2945  return(RailGraphics->Code_n);
2946 
2947  case 'o':
2948  return(RailGraphics->Code_o);
2949 
2950  case 'p':
2951  return(RailGraphics->Code_p);
2952 
2953  case 'q':
2954  return(RailGraphics->Code_q);
2955 
2956  case 'r':
2957  return(RailGraphics->Code_r);
2958 
2959  case 's':
2960  return(RailGraphics->Code_s);
2961 
2962  case 't':
2963  return(RailGraphics->Code_t);
2964 
2965  case 'u':
2966  return(RailGraphics->Code_u);
2967 
2968  case 'v':
2969  return(RailGraphics->Code_v);
2970 
2971  case 'w':
2972  return(RailGraphics->Code_w);
2973 
2974  case 'x':
2975  return(RailGraphics->Code_x);
2976 
2977  case 'y':
2978  return(RailGraphics->Code_y);
2979 
2980  case 'z':
2981  return(RailGraphics->Code_z);
2982 
2983  default:
2984  return(RailGraphics->TempHeadCode);
2985  }
2986 }
2987 
2988 // ----------------------------------------------------------------------------
2989 
2990 void TTrain::SetHeadCodeGraphics(int Caller, AnsiString Code)
2991 {
2992  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetHeadCodeGraphics," + HeadCode);
2993  if(Code.Length() != 4)
2994  {
2995  TrainController->StopTTClockMessage(62, "Headcode Incorrect length");
2996  }
2997  for(int x = 1; x < 5; x++) // AnsiString indices start at 1
2998  {
2999  HeadCodeGrPtr[x - 1]->Assign(SetOneGraphicCode(Code[x]));
3000  }
3001  if(BackgroundColour != clB5G5R5)
3002  // i.e. not the basic graphic colour as loaded from resource file
3003  {
3004  for(int x = 0; x < 4; x++)
3005  {
3007  }
3008  }
3009  Utilities->CallLogPop(1484);
3010 }
3011 
3012 // ----------------------------------------------------------------------------
3013 
3014 void TTrain::GetLeadElement(int Caller)
3015 // assumes Mid & Lag already set, sets LeadElement,
3016 // LeadEntryPos, LeadExitPos & DerailPending (don't want to act on it immediately)
3017 {
3018  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetLeadElement," + HeadCode);
3019  DerailPending = false;
3023  {
3024  // attr 0=straight, - links 0 & 1 (0 = lead)
3025  // attr 1=diverging, - links 2 & 3 (2 = lead)
3026  // set appropriate next element or derail - use a subroutine & return element & bool for derail
3027  // points always have links 0 & 2 = lead, link 1 = trailing straight, link 3 = training diverging
3028 
3029  // if enter at lead, exit at whatever attr set at
3030  // if enter at lag, exit at lead, but set derail wrt attribute
3031  if((LeadEntryPos == 0) && (Track->TrackElementAt(272, LeadElement).Attribute == 0))
3032  {
3033  LeadExitPos = 1;
3034  }
3035 
3036  // strictly speaking shouldn't need to set to 0 and 2 correctly since TrackIsInARoute caters for both, but
3037  // best to be on safe side
3038  else if(LeadEntryPos == 0)
3039  {
3040  LeadEntryPos = 2;
3041  LeadExitPos = 3;
3042  }
3043  else if((LeadEntryPos == 2) && (Track->TrackElementAt(273, LeadElement).Attribute == 0))
3044  {
3045  LeadEntryPos = 0;
3046  LeadExitPos = 1;
3047  }
3048  else if(LeadEntryPos == 2)
3049  {
3050  LeadExitPos = 3;
3051  }
3052  else if((LeadEntryPos == 1) && (Track->TrackElementAt(274, LeadElement).Attribute == 0))
3053  {
3054  LeadExitPos = 0;
3055  }
3056  else if(LeadEntryPos == 1)
3057  {
3058  LeadExitPos = 0;
3059  DerailPending = true;
3060  }
3061  else if((LeadEntryPos == 3) && (Track->TrackElementAt(275, LeadElement).Attribute == 0))
3062  {
3063  LeadExitPos = 0;
3064  DerailPending = true;
3065  }
3066  else if(LeadEntryPos == 3)
3067  {
3068  LeadExitPos = 0;
3069  }
3070  }
3071  else if(LeadEntryPos == 0)
3072  {
3073  LeadExitPos = 1;
3074  }
3075  else if(LeadEntryPos == 1)
3076  {
3077  LeadExitPos = 0;
3078  }
3079  else if(LeadEntryPos == 2)
3080  {
3081  LeadExitPos = 3;
3082  }
3083  else if(LeadEntryPos == 3)
3084  {
3085  LeadExitPos = 2;
3086  }
3087  // TTrackElement TrackElement = Track->TrackElementAt(276, LeadElement);
3088 /* signal check moved to Update() function
3089  if((TrackElement.TrackType == SignalPost) && (TrackElement.Config[LeadExitPos] == Signal)
3090  && (TrackElement.Attribute == 0))//0 = red
3091  {
3092  StoppedAtSignal = true; //comment out for test of locked route graphic replot
3093  }
3094  else
3095  {
3096  StoppedAtSignal = false;
3097  }
3098 */
3099  Utilities->CallLogPop(662);
3100 }
3101 
3102 // ----------------------------------------------------------------------------
3103 
3104 void TTrain::GetOffsetValues(int Caller, int &HOffset, int &VOffset, int Link) const
3105 {
3106  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetOffsetValues," + AnsiString(Link) + "," + HeadCode);
3107  switch(Link)
3108  {
3109  case 1:
3110  {
3111  HOffset = 0;
3112  VOffset = 0;
3113  break;
3114  }
3115 
3116  case 2:
3117  {
3118  HOffset = 4;
3119  VOffset = 0;
3120  break;
3121  }
3122 
3123  case 3:
3124  {
3125  HOffset = 8;
3126  VOffset = 0;
3127  break;
3128  }
3129 
3130  case 4:
3131  {
3132  HOffset = 0;
3133  VOffset = 4;
3134  break;
3135  }
3136 
3137  case 6:
3138  {
3139  HOffset = 8;
3140  VOffset = 4;
3141  break;
3142  }
3143 
3144  case 7:
3145  {
3146  HOffset = 0;
3147  VOffset = 8;
3148  break;
3149  }
3150 
3151  case 8:
3152  {
3153  HOffset = 4;
3154  VOffset = 8;
3155  break;
3156  }
3157 
3158  case 9:
3159  {
3160  HOffset = 8;
3161  VOffset = 8;
3162  break;
3163  }
3164 
3165  default:
3166  {
3167  throw Exception("Error in GetOffsetValues - Link value wrong");
3168  }
3169  }
3170  Utilities->CallLogPop(674);
3171 }
3172 
3173 // ---------------------------------------------------------------------------
3174 
3175 bool TTrain::LowEntryValue(int EntryLink) const
3176 {
3177 /* returns true if EntryLink is 1, 2, 4 or 7, in these circumstances the front of the train (i.e.
3178  the character that is red or blue) is the last character of the headcode, otherwise it's the first character of the headcode
3179 */
3180  if((EntryLink == 1) || (EntryLink == 2) || (EntryLink == 4) || (EntryLink == 7))
3181  {
3182  return(true);
3183  }
3184  else
3185  {
3186  return(false);
3187  }
3188 }
3189 
3190 // ---------------------------------------------------------------------------
3191 
3192 void TTrain::PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
3193 {
3194  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PickUpBackgroundBitmap," + AnsiString(HOffset) + "," +
3195  AnsiString(VOffset) + "," + AnsiString(Element) + "," + AnsiString(EntryPos) + "," + HeadCode);
3196  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
3197  // default values
3198  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
3199 
3200  TAllRoutes::TRouteType RouteType;
3201 
3202  RouteType = AllRoutes->GetRouteTypeAndGraphics(11, Element, EntryPos, EXGraphicPtr, EntryDirectionGraphicPtr);
3203 
3204  TRect SourceRect, DestRect;
3205 
3206  DestRect.init(0, 0, 8, 8); // initialise left, top, right, bottom
3207  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3208  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3209  Graphics::TBitmap *TempGraphic = new Graphics::TBitmap;
3210 
3211  TempGraphic->PixelFormat = pf8bit;
3212  TempGraphic->Width = 16;
3213  TempGraphic->Height = 16;
3214  TTrackElement TempElement = Track->TrackElementAt(286, Element);
3215 
3216  if(TempElement.TrackType == Points)
3217  {
3218  TempGraphic->Assign(TempElement.GraphicPtr);
3219  TempGraphic->Transparent = true;
3220  TempGraphic->TransparentColor = Utilities->clTransparent;
3221  if(RouteType == TAllRoutes::AutoSigsRoute)
3222  {
3223  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3224  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3225  }
3226  else
3227  {
3228  TempGraphic->Canvas->Draw(0, 0, Track->GetFilletGraphic(1, TempElement)); // add fillet
3229  }
3230  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3231  }
3232  else if(TempElement.TrackType == GapJump) // plot set gap
3233  {
3234  if(TempElement.SpeedTag == 88)
3235  {
3236  TempGraphic->Assign(RailGraphics->gl88set);
3237  }
3238  else if(TempElement.SpeedTag == 89)
3239  {
3240  TempGraphic->Assign(RailGraphics->gl89set);
3241  }
3242  else if(TempElement.SpeedTag == 90)
3243  {
3244  TempGraphic->Assign(RailGraphics->gl90set);
3245  }
3246  else if(TempElement.SpeedTag == 91)
3247  {
3248  TempGraphic->Assign(RailGraphics->gl91set);
3249  }
3250  else if(TempElement.SpeedTag == 92)
3251  {
3252  TempGraphic->Assign(RailGraphics->gl92set);
3253  }
3254  else if(TempElement.SpeedTag == 93)
3255  {
3256  TempGraphic->Assign(RailGraphics->bm93set);
3257  }
3258  else if(TempElement.SpeedTag == 94)
3259  {
3260  TempGraphic->Assign(RailGraphics->bm94set);
3261  }
3262  else if(TempElement.SpeedTag == 95)
3263  {
3264  TempGraphic->Assign(RailGraphics->gl95set);
3265  }
3266  TempGraphic->Transparent = true;
3267  TempGraphic->TransparentColor = Utilities->clTransparent;
3268  if(RouteType == TAllRoutes::AutoSigsRoute)
3269  {
3270  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3271  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3272  }
3273  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3274  }
3275  // new for version 0.6
3276  else if(TempElement.TrackType == SignalPost)
3277  {
3278  if(TempElement.SigAspect == TTrackElement::GroundSignal)
3279  {
3280  for(int x = 0; x < 40; x++)
3281  {
3282  if((Track->SigTableGroundSignal[x].SpeedTag == TempElement.SpeedTag) && (Track->SigTableGroundSignal[x].Attribute == 0))
3283  // need to stop aspect
3284  {
3285  TempGraphic->Assign(Track->SigTableGroundSignal[x].SigPtr);
3286  break;
3287  }
3288  }
3289  }
3290  else // normal signal
3291  {
3292  TempGraphic->Assign(TempElement.GraphicPtr);
3293  // GraphicPtr set to normal signal in a signal track element
3294  }
3295  TempGraphic->Transparent = true;
3296  TempGraphic->TransparentColor = Utilities->clTransparent;
3297  if(RouteType == TAllRoutes::AutoSigsRoute)
3298  {
3299  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3300  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3301  }
3302  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3303  }
3304  else
3305  {
3306  // first check if there's a NamedNonStationLocation element at that position & if so pick up that as the background
3307  // can't name points gaps or signals so 'else' OK
3308  bool FoundFlag;
3309  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap(4, TempElement.HLoc, TempElement.VLoc, FoundFlag);
3310  if(FoundFlag)
3311  {
3313  {
3314  GraphicPtr->Canvas->CopyRect(DestRect, Track->InactiveTrackElementAt(26, IMPair.first).GraphicPtr->Canvas, SourceRect);
3315  TempGraphic->Assign(RailGraphics->bmName);
3316  TempGraphic->Transparent = true;
3317  TempGraphic->TransparentColor = Utilities->clTransparent;
3318  if(RouteType == TAllRoutes::AutoSigsRoute)
3319  {
3320  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3321  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3322  }
3323  else
3324  {
3325  TempGraphic->Canvas->Draw(0, 0, TempElement.GraphicPtr);
3326  }
3327  // draw track on top
3328  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3329  }
3330  else if(Track->InactiveTrackElementAt(116, IMPair.first).TrackType == LevelCrossing)
3331  {
3332  TempGraphic->Assign(TempElement.GraphicPtr);
3333  TempGraphic->Transparent = true;
3334  TempGraphic->TransparentColor = Utilities->clTransparent;
3335  // note that can't be an AutoSigsRoute
3336  // now overlay the LC central portion
3337  int BDVectorPos = -1; //not used
3338  if(Track->AnyLinkedBarrierDownVectorManual(0, Track->InactiveTrackElementAt(130, IMPair.first).HLoc, Track->InactiveTrackElementAt(131, IMPair.first).VLoc, BDVectorPos))
3339  {
3340  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlainMan);
3341  }
3342  else
3343  {
3344  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlain);
3345  }
3346  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3347  }
3348  else
3349  {
3350  TempGraphic->Assign(TempElement.GraphicPtr);
3351  TempGraphic->Transparent = true;
3352  TempGraphic->TransparentColor = Utilities->clTransparent;
3353  if(RouteType == TAllRoutes::AutoSigsRoute)
3354  {
3355  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3356  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3357  }
3358  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3359  }
3360  }
3361  else
3362  {
3363  TempGraphic->Assign(TempElement.GraphicPtr);
3364  TempGraphic->Transparent = true;
3365  TempGraphic->TransparentColor = Utilities->clTransparent;
3366  if(RouteType == TAllRoutes::AutoSigsRoute)
3367  {
3368  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3369  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3370  }
3371  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3372  }
3373  }
3374  delete TempGraphic;
3375  Utilities->CallLogPop(675);
3376 }
3377 
3378 // ---------------------------------------------------------------------------
3379 
3380 // This was an attempt to pick up the actual 8x8 graphic from the display, so that text & user graphics would show as soon as the train passed, and overwrite it with the
3381 // reconstructed track, and it works ok but for the little arrows showing route directions at start and end, which extend beyond the track. It doesn't matter for autosig
3382 // routes because they are replotted (along with the direction arrows) but for others they shouldn't be. Leave in in case an easy way to remove these pointers comes to mind.
3383 /*
3384 
3385  void TTrain::PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
3386  {
3387  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PickUpBackgroundBitmap," + AnsiString(HOffset) +
3388  "," + AnsiString(VOffset) + "," + AnsiString(Element) + "," + AnsiString(EntryPos) + "," + HeadCode);
3389  TAllRoutes::TRouteType RouteType;
3390  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
3391  // default values
3392  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
3393  TTrackElement TempElement = Track->TrackElementAt(, Element); //this is a copy of the element passed into the function
3394  TRect SourceRect, DestRect, ScreenSourceRect;
3395  RouteType = AllRoutes->GetRouteTypeAndGraphics(7, Element, EntryPos, EXGraphicPtr, EntryDirectionGraphicPtr);
3396 
3397  DestRect.init(0, 0, 8, 8); //initialise left, top, right, bottom
3398  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3399  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3400 
3401  //add text & user graphics if any to *GraphicPtr prior to adding the track
3402  int Left = ((TempElement.HLoc - Display->DisplayOffsetH) * 16) + HOffset;
3403  int Top = ((TempElement.VLoc - Display->DisplayOffsetV) * 16) + VOffset;
3404  int Right = Left + 8;
3405  int Bottom = Top + 8;
3406  ScreenSourceRect.init(Left, Top, Right, Bottom);
3407  GraphicPtr->Canvas->CopyMode = cmSrcCopy;
3408  GraphicPtr->Canvas->CopyRect(DestRect, Display->GetImage()->Canvas, ScreenSourceRect);
3409 
3410  Graphics::TBitmap *TempGraphic = new Graphics::TBitmap; //this will hold the 16x16 reconstructed element, prior to transfer of the 8x8 bit to *GraphicPtr
3411  TempGraphic->PixelFormat = pf8bit;
3412  TempGraphic->Width = 16;
3413  TempGraphic->Height = 16;
3414 
3415  Graphics::TBitmap *SourceGraphic = new Graphics::TBitmap; //this will hold the 8x8 element segment from TempGraphic - needed because to keep transparency have to use Draw, not CopyRect
3416  SourceGraphic->PixelFormat = pf8bit;
3417  SourceGraphic->Width = 16;
3418  SourceGraphic->Height = 16;
3419  SourceGraphic->Transparent = true;
3420  SourceGraphic->TransparentColor = Utilities->clTransparent;
3421 
3422  if (TempElement.TrackType == Points)
3423  {
3424  TempGraphic->Assign(TempElement.GraphicPtr);
3425  TempGraphic->Transparent = true;
3426  TempGraphic->TransparentColor = Utilities->clTransparent;
3427  if (RouteType == TAllRoutes::AutoSigsRoute)
3428  {
3429  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3430  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3431  }
3432  else
3433  {
3434  TempGraphic->Canvas->Draw(0, 0, Track->GetFilletGraphic(1, TempElement)); // add fillet
3435  }
3436  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3437  }
3438  else if (TempElement.TrackType == GapJump) // plot set gap
3439  {
3440  if (TempElement.SpeedTag == 88)
3441  TempGraphic->Assign(RailGraphics->gl88set);
3442  else if (TempElement.SpeedTag == 89)
3443  TempGraphic->Assign(RailGraphics->gl89set);
3444  else if (TempElement.SpeedTag == 90)
3445  TempGraphic->Assign(RailGraphics->gl90set);
3446  else if (TempElement.SpeedTag == 91)
3447  TempGraphic->Assign(RailGraphics->gl91set);
3448  else if (TempElement.SpeedTag == 92)
3449  TempGraphic->Assign(RailGraphics->gl92set);
3450  else if (TempElement.SpeedTag == 93)
3451  TempGraphic->Assign(RailGraphics->bm93set);
3452  else if (TempElement.SpeedTag == 94)
3453  TempGraphic->Assign(RailGraphics->bm94set);
3454  else if (TempElement.SpeedTag == 95)
3455  TempGraphic->Assign(RailGraphics->gl95set);
3456  TempGraphic->Transparent = true;
3457  TempGraphic->TransparentColor = Utilities->clTransparent;
3458  if (RouteType == TAllRoutes::AutoSigsRoute) {
3459  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3460  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3461  }
3462  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3463  }
3464  // new for version 0.6
3465  else if (TempElement.TrackType == SignalPost)
3466  {
3467  if (TempElement.SigAspect == TTrackElement::GroundSignal)
3468  {
3469  for (int x = 0; x < 40; x++)
3470  {
3471  if ((Track->SigTableGroundSignal[x].SpeedTag == TempElement.SpeedTag) && (Track->SigTableGroundSignal[x].Attribute == 0))
3472  // need to stop aspect
3473  {
3474  TempGraphic->Assign(Track->SigTableGroundSignal[x].SigPtr);
3475  break;
3476  }
3477  }
3478  }
3479  else // normal signal
3480  {
3481  TempGraphic->Assign(TempElement.GraphicPtr);
3482  // GraphicPtr set to normal signal in a signal track element
3483  }
3484  TempGraphic->Transparent = true;
3485  TempGraphic->TransparentColor = Utilities->clTransparent;
3486  if (RouteType == TAllRoutes::AutoSigsRoute) {
3487  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3488  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3489  }
3490  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3491  }
3492  else {
3493  // first check if there's a NamedNonStationLocation element at that position & if so pick up that as the background
3494  // can't name points gaps or signals so 'else' OK
3495  bool FoundFlag;
3496  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap
3497  (4, TempElement.HLoc, TempElement.VLoc, FoundFlag);
3498  if (FoundFlag)
3499  {
3500  if (Track->InactiveTrackElementAt(, IMPair.first).TrackType == NamedNonStationLocation)
3501  {
3502  GraphicPtr->Canvas->CopyRect(DestRect,
3503  Track->InactiveTrackElementAt(, IMPair.first).GraphicPtr->Canvas, SourceRect);
3504  TempGraphic->Assign(RailGraphics->bmName);
3505  TempGraphic->Transparent = true;
3506  TempGraphic->TransparentColor = Utilities->clTransparent;
3507  if (RouteType == TAllRoutes::AutoSigsRoute)
3508  {
3509  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3510  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3511  }
3512  else
3513  TempGraphic->Canvas->Draw(0, 0, TempElement.GraphicPtr);
3514  // draw track on top
3515  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3516  SourceRect);
3517  }
3518  else if (Track->InactiveTrackElementAt(, IMPair.first).TrackType == LevelCrossing) {
3519  TempGraphic->Assign(TempElement.GraphicPtr);
3520  TempGraphic->Transparent = true;
3521  TempGraphic->TransparentColor = Utilities->clTransparent;
3522  // note that can't be an AutoSigsRoute
3523  // now overlay the LC central portion
3524  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlain);
3525  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3526  SourceRect);
3527  }
3528  else {
3529  TempGraphic->Assign(TempElement.GraphicPtr);
3530  TempGraphic->Transparent = true;
3531  TempGraphic->TransparentColor = Utilities->clTransparent;
3532  if (RouteType == TAllRoutes::AutoSigsRoute) {
3533  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3534  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3535  }
3536  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3537  SourceRect);
3538  }
3539  }
3540  else {
3541  TempGraphic->Assign(TempElement.GraphicPtr);
3542  TempGraphic->Transparent = true;
3543  TempGraphic->TransparentColor = Utilities->clTransparent;
3544  if (RouteType == TAllRoutes::AutoSigsRoute) {
3545  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3546  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3547  }
3548  SourceGraphic->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3549  GraphicPtr->Canvas->Draw(0, 0, SourceGraphic);
3550  }
3551  }
3552  delete TempGraphic;
3553  delete SourceGraphic;
3554  Utilities->CallLogPop();
3555  }
3556 */
3557 // ---------------------------------------------------------------------------
3558 
3559 void TTrain::PlotTrainGraphic(int Caller, int ArrayNumber, TDisplay *Disp)
3560 {
3561  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainGraphic," + AnsiString(ArrayNumber) + "," + HeadCode);
3562  if(PlotElement[ArrayNumber] == -1)
3563  {
3564  Utilities->CallLogPop(676);
3565  return; // not plotted yet
3566  }
3567  SetTrainElementID(0, PlotElement[ArrayNumber], PlotEntryPos[ArrayNumber]);
3568  // set before plot so gap flashing stops first
3569  Disp->PlotOutput(29, ((Track->TrackElementAt(295, PlotElement[ArrayNumber]).HLoc * 16) + HOffset[ArrayNumber]),
3570  ((Track->TrackElementAt(296, PlotElement[ArrayNumber]).VLoc * 16) + VOffset[ArrayNumber]), HeadCodePosition[ArrayNumber]);
3571  // Only need to set ID for leading element, stays set until train finally leaves the element
3572  Plotted = true;
3573  Utilities->CallLogPop(677);
3574 }
3575 
3576 // ---------------------------------------------------------------------------
3577 
3578 void TTrain::PlotBackgroundGraphic(int Caller, int ArrayNumber, TDisplay *Disp) const
3579 {
3580  Disp->PlotOutput(30, ((Track->TrackElementAt(297, PlotElement[ArrayNumber]).HLoc * 16) + HOffset[ArrayNumber]),
3581  ((Track->TrackElementAt(298, PlotElement[ArrayNumber]).VLoc * 16) + VOffset[ArrayNumber]), BackgroundPtr[ArrayNumber]);
3582 }
3583 
3584 // ---------------------------------------------------------------------------
3585 
3586 bool TTrain::BufferAtExit(int Caller, int Element, int ExitPos) const
3587 {
3588  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",BufferAtExit," + AnsiString(Element) + "," + AnsiString(ExitPos) + "," +
3589  HeadCode);
3590  if((Track->TrackElementAt(299, Element).TrackType == Buffers) && (Track->TrackElementAt(300, Element).Config[ExitPos] == End))
3591  {
3592  Utilities->CallLogPop(678);
3593  return(true);
3594  }
3595  else
3596  {
3597  Utilities->CallLogPop(679);
3598  return(false);
3599  }
3600 }
3601 
3602 // ---------------------------------------------------------------------------
3603 
3604 bool TTrain::ContinuationExit(int Caller, int Element, int ExitPos) const
3605 {
3606  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ContinuationExit," + AnsiString(Element) + "," + AnsiString(ExitPos) +
3607  "," + HeadCode);
3608  if((Track->TrackElementAt(301, Element).TrackType == Continuation) && (Track->TrackElementAt(302, Element).Config[ExitPos] == End))
3609  {
3610  Utilities->CallLogPop(680);
3611  return(true);
3612  }
3613  else
3614  {
3615  Utilities->CallLogPop(681);
3616  return(false);
3617  }
3618 }
3619 
3620 // ---------------------------------------------------------------------------
3621 
3622 bool TTrain::IsTrainIDOnBridgeTrackPos01(int Caller, unsigned int TrackVectorPosition)
3623 // test whether this train on a bridge on trackpos 0 & 1
3624 {
3625  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainIDOnBridgeTrackPos01," + AnsiString(TrackVectorPosition) + "," +
3626  HeadCode);
3627  if(Track->TrackElementAt(303, TrackVectorPosition).TrackType != Bridge)
3628  {
3629  Utilities->CallLogPop(682);
3630  return(false);
3631  }
3632  // if(Track->TrackElementAt(304, TrackVectorPosition).TrainIDOnElement != TrainID) return false; No, if a bridge could be one of 2 TrainIDs
3634  {
3636  {
3637  throw Exception("Error, same train on two different bridge tracks");
3638  }
3639  else
3640  {
3641  Utilities->CallLogPop(684);
3642  return(true);
3643  }
3644  }
3645  else
3646  {
3647  Utilities->CallLogPop(685);
3648  return(false);
3649  }
3650 }
3651 
3652 // ---------------------------------------------------------------------------
3653 
3654 bool TTrain::IsTrainIDOnBridgeTrackPos23(int Caller, unsigned int TrackVectorPosition)
3655 // test whether this train on a bridge on trackpos 2 & 3
3656 {
3657  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainIDOnBridgeTrackPos23," + AnsiString(TrackVectorPosition) + "," +
3658  HeadCode);
3659  if(Track->TrackElementAt(307, TrackVectorPosition).TrackType != Bridge)
3660  {
3661  Utilities->CallLogPop(686);
3662  return(false);
3663  }
3664  // if(Track->TrackElementAt(308, TrackVectorPosition).TrainIDOnElement != TrainID) return false; No, if a bridge could be one of 2 TrainIDs
3666  {
3667  // don't carry out check for train on tracks 0 & 1 else will enter an infinite loop if train on both
3668  Utilities->CallLogPop(687);
3669  return(true);
3670  }
3671  else
3672  {
3673  Utilities->CallLogPop(688);
3674  return(false);
3675  }
3676 }
3677 
3678 // ---------------------------------------------------------------------------
3679 
3680 void TTrain::SetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
3681 {
3682  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetTrainElementID," + AnsiString(TrackVectorPosition) + "," +
3683  AnsiString(EntryPos) + "," + HeadCode);
3684  Track->TrackElementAt(310, TrackVectorPosition).TrainIDOnElement = TrainID;
3685 
3686  // unplot GapFlash graphics if land on flashing gap (this done before train plotted - see PlotTrainGraphic)
3687  if(Track->GapFlashFlag)
3688  {
3690  {
3693  Track->GapFlashFlag = false;
3694  }
3695  }
3696  if(Track->TrackElementAt(311, TrackVectorPosition).TrackType == Bridge)
3697  {
3698  if(EntryPos == -1)
3699  {
3700  throw Exception("Error, TrackVectorPosition set but not EntryPos in SetTrainElementID");
3701  }
3702  if(EntryPos < 2)
3703  {
3705  }
3706  else
3707  {
3709  }
3710  }
3711  Utilities->CallLogPop(690);
3712 }
3713 
3714 // ---------------------------------------------------------------------------
3715 
3716 void TTrain::ResetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
3717 {
3718  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ResetTrainElementID," + AnsiString(TrackVectorPosition) + "," +
3719  AnsiString(EntryPos) + "," + HeadCode);
3720  if(Track->TrackElementAt(314, TrackVectorPosition).TrackType != Bridge)
3721  {
3722  Track->TrackElementAt(315, TrackVectorPosition).TrainIDOnElement = -1;
3723  }
3724  else
3725  {
3726  if(EntryPos == -1)
3727  {
3728  throw Exception("Error, TrackVectorPosition set but not EntryPos in ResetTrainElementID");
3729  }
3730  if(EntryPos < 2)
3731  {
3732  Track->TrackElementAt(316, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 = -1;
3733  }
3734  else
3735  {
3736  Track->TrackElementAt(317, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 = -1;
3737  }
3738  if((EntryPos < 2) && (Track->TrackElementAt(318, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
3739  // i.e. other train on track 2&3
3740  {
3741  Track->TrackElementAt(319, TrackVectorPosition).TrainIDOnElement = Track->TrackElementAt(320, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
3742  }
3743  else if((EntryPos > 1) && (Track->TrackElementAt(321, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
3744  // i.e. other train on track 1&2
3745  {
3746  Track->TrackElementAt(322, TrackVectorPosition).TrainIDOnElement = Track->TrackElementAt(323, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
3747  }
3748  else
3749  {
3750  Track->TrackElementAt(324, TrackVectorPosition).TrainIDOnElement = -1;
3751  }
3752  }
3753  Utilities->CallLogPop(691);
3754 }
3755 
3756 // ---------------------------------------------------------------------------
3757 
3758 void TTrain::PlotAlternativeTrackRouteGraphic(int Caller, unsigned int ElementVecNum, int ElementEntryPos, int HOffset, int VOffset, TStraddle StraddleValue)
3759 {
3760  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotAlternativeTrackRouteGraphic," + AnsiString(ElementVecNum) + "," +
3761  AnsiString(ElementEntryPos) + "," + AnsiString(HOffset) + "," + AnsiString(VOffset) + "," + AnsiString(StraddleValue) + "," + HeadCode);
3762  int LockedVectorNumber;
3763 
3764  if(Track->TrackElementAt(325, ElementVecNum).TrackType != Bridge)
3765  // && (Track->TrackElementAt(326, ElementVecNum).TrackType != Crossover))
3766  {
3767  // only applies for a bridge as there can't be (or shouldn't be) 2 routes on an element that isn't a bridge
3768  Utilities->CallLogPop(692);
3769  return;
3770  }
3771  if(AllRoutes->TrackIsInARoute(0, ElementVecNum, (3 - ElementEntryPos)))
3772  // i.e other track is in a marked route
3773  // LinkPos doesn't have to be the entry position for the above check
3774  {
3775  TRect SourceRect, DestRect;
3776  DestRect.init(0, 0, 8, 8); // left, top, right, bottom
3777  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3778  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3779  // identify the route element for the other track
3780  TAllRoutes::TRouteElementPair RoutePair1, RoutePair2;
3781  RoutePair1 = AllRoutes->GetRouteElementDataFromRoute2MultiMap(13, Track->TrackElementAt(327, ElementVecNum).HLoc,
3782  Track->TrackElementAt(328, ElementVecNum).VLoc, RoutePair2);
3783  int FirstELink, SecondELink = -1;
3784  FirstELink = AllRoutes->GetFixedRouteAt(149, RoutePair1.first).GetFixedPrefDirElementAt(159, RoutePair1.second).GetELink();
3785  // must be at least one
3786  if(RoutePair2.first > -1)
3787  {
3788  SecondELink = AllRoutes->GetFixedRouteAt(150, RoutePair2.first).GetFixedPrefDirElementAt(160, RoutePair2.second).GetELink();
3789  }
3790  TPrefDirElement RouteElement;
3791  // Graphics::TBitmap *RouteGraphic;
3792  if(FirstELink == Track->TrackElementAt(329, ElementVecNum).Link[ElementEntryPos])
3793  // i.e. other track is in RoutePair2
3794  {
3795  if(SecondELink == -1)
3796  {
3797  throw Exception("Error - Second ELink should be set but isn't in PlotAlternativeTrackRouteGraphic [1]");
3798  }
3799  if(SecondELink == Track->TrackElementAt(330, ElementVecNum).Link[ElementEntryPos])
3800  // error if both have same Link number
3801  {
3802  throw Exception("Error - First & Second ELinks have same value in PlotAlternativeTrackRouteGraphic");
3803  }
3804  // RouteGraphic = AllRoutes->GetFixedRouteAt(151, RoutePair2.first).GetFixedPrefDirElementAt(161, RoutePair2.second).GetEXGraphicPtr();
3805  RouteElement = AllRoutes->GetFixedRouteAt(152, RoutePair2.first).GetFixedPrefDirElementAt(162, RoutePair2.second);
3806  }
3807  else // other track is in RoutePair1
3808  {
3809  // RouteGraphic = AllRoutes->GetFixedRouteAt(153, RoutePair1.first).GetFixedPrefDirElementAt(163, RoutePair1.second).GetEXGraphicPtr();
3810  RouteElement = AllRoutes->GetFixedRouteAt(154, RoutePair1.first).GetFixedPrefDirElementAt(164, RoutePair1.second);
3811  }
3812  Graphics::TBitmap *DestGraphic = new Graphics::TBitmap;
3813  DestGraphic->PixelFormat = pf8bit;
3814  DestGraphic->Width = 8;
3815  DestGraphic->Height = 8;
3816  DestGraphic->Transparent = true;
3817  // has to be transparent or will overwrite the track that the train has just left
3818  DestGraphic->TransparentColor = Utilities->clTransparent;
3819  DestGraphic->Canvas->CopyRect(DestRect, RouteElement.GetRouteEXGraphicPtr()->Canvas, SourceRect);
3820  Display->PlotOutput(31, (Track->TrackElementAt(331, ElementVecNum).HLoc * 16) + HOffset,
3821  (Track->TrackElementAt(332, ElementVecNum).VLoc * 16) + VOffset, DestGraphic);
3822  // plot locked route marker for other route if appropriate
3823  TPrefDirElement PrefDirElement; // holder for next call, unused
3824  // plot locked route marker if appropriate, but only when train leaves element completely as this is a 16x16 graphic
3825  if(StraddleValue == LeadMidLag)
3826  {
3828  PrefDirElement, LockedVectorNumber))
3829  {
3830  Display->PlotOutput(32, (Track->TrackElementAt(333, ElementVecNum).HLoc * 16), (Track->TrackElementAt(334, ElementVecNum).VLoc * 16),
3831  RailGraphics->LockedRouteCancelPtr[RouteElement.GetELink()]);
3832  }
3833  }
3834  delete DestGraphic;
3835  }
3836  // but - there may be a train on the other track - if so need to replot it else the section of route overwrites it
3837  // also can only be a bridge or trains either have already or soon will crash
3838  if(Track->TrackElementAt(335, ElementVecNum).TrackType != Bridge)
3839  {
3840  Utilities->CallLogPop(695);
3841  return;
3842  }
3843  if(ElementEntryPos > 1) // other train is on track 01
3844  {
3846  {
3848  }
3849  }
3850  else // other train is on track 23
3851  {
3853  {
3855  }
3856  }
3857  Utilities->CallLogPop(696);
3858 }
3859 
3860 // ---------------------------------------------------------------------------
3861 
3862 void TTrain::CheckAndCancelRouteForWrongEndEntry(int Caller, int Element, int EntryPos)
3863 {
3864  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckAndCancelRouteForWrongEndEntry," + AnsiString(Element) + "," +
3865  AnsiString(EntryPos) + "," + HeadCode);
3866  int RouteNumber;
3867  bool WrongRoute = false;
3868  TPrefDirElement RouteElement;
3870  TAllRoutes::TRoute2MultiMapIterator Route2MultiMapIterator;
3871 
3872  if(AllRoutes->GetRouteTypeAndNumber(11, Element, EntryPos, RouteNumber) == TAllRoutes::NoRoute)
3873  // here if single track element & no route, or double track element with no route at EntryPos but still need to check if on points or a crossover on non-route track,
3874  // and force-erase route if so (bridge OK of course) note that GetRouteTypeAndNumber allows for points having an EntryPos of 0 or 2 & still returns correct values
3875  {
3876  if((Track->TrackElementAt(340, Element).TrackType == Crossover) || (Track->TrackElementAt(341, Element).TrackType == Points))
3877  {
3878  if(AllRoutes->GetRouteTypeAndNumber(12, Element, (3 - EntryPos), RouteNumber) != TAllRoutes::NoRoute)
3879  // (3-EntryPos) guarantees other route (0->3; 1->2; 2->1; 3->0)
3880  {
3881  if(AllRoutes->GetFixedRouteAt(179, RouteNumber).PrefDirSize() > 2)
3882  {
3883  // don't call for stub end routes
3885  }
3886  AllRoutes->GetModifiableRouteAt(13, RouteNumber).ForceCancelRoute(1);
3887  Utilities->CallLogPop(697);
3888  return;
3889  }
3890  }
3891  // also need to check for a route on a crossing diagonal
3892  TTrackElement TrackElement = Track->TrackElementAt(892, Element);
3893  int LinkNumber = TrackElement.Link[EntryPos];
3894  if((LinkNumber == 1) || (LinkNumber == 3) || (LinkNumber == 7) || (LinkNumber == 9))
3895  {
3896  if(AllRoutes->DiagonalFouledByRoute(0, TrackElement.HLoc, TrackElement.VLoc, LinkNumber))
3897  {
3898  // for LinkNumber = 1, potentially fouled diagonals are at H-1, V, Lk 3 & H, V-1, Lk 7
3899  bool LogActionErrorCalled = false;
3900  // to ensure only called once if have 2 routes on the 2 crossed diagonals
3901  if(LinkNumber == 1)
3902  {
3903  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(0, TrackElement.HLoc - 1, TrackElement.VLoc, 3, RouteNumber))
3904  {
3905  if(AllRoutes->GetFixedRouteAt(207, RouteNumber).PrefDirSize() > 2)
3906  {
3907  // don't call for stub end routes
3909  LogActionErrorCalled = true;
3910  }
3911  AllRoutes->GetModifiableRouteAt(20, RouteNumber).ForceCancelRoute(3);
3912  }
3913  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(1, TrackElement.HLoc, TrackElement.VLoc - 1, 7, RouteNumber))
3914  // not else in case have different routes on each diagonal, though shouldn't be possible
3915  {
3916  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(208, RouteNumber).PrefDirSize() > 2)
3917  {
3918  // don't call for stub end routes
3920  }
3921  AllRoutes->GetModifiableRouteAt(21, RouteNumber).ForceCancelRoute(4);
3922  }
3923  }
3924 
3925  // for LinkNumber = 3, potentially fouled diagonals are at H+1, V, Lk 1 & H, V-1 Lk 9
3926  else if(LinkNumber == 3)
3927  {
3928  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(2, TrackElement.HLoc + 1, TrackElement.VLoc, 1, RouteNumber))
3929  {
3930  if(AllRoutes->GetFixedRouteAt(209, RouteNumber).PrefDirSize() > 2)
3931  {
3932  // don't call for stub end routes
3934  LogActionErrorCalled = true;
3935  }
3936  AllRoutes->GetModifiableRouteAt(22, RouteNumber).ForceCancelRoute(5);
3937  }
3938  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(3, TrackElement.HLoc, TrackElement.VLoc - 1, 9, RouteNumber))
3939  // not else in case have different routes on each diagonal, though shouldn't be possible
3940  {
3941  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(210, RouteNumber).PrefDirSize() > 2)
3942  {
3943  // don't call for stub end routes
3945  }
3946  AllRoutes->GetModifiableRouteAt(23, RouteNumber).ForceCancelRoute(6);
3947  }
3948  }
3949 
3950  // for LinkNumber = 7, potentially fouled diagonals are at H-1, V, Lk 9 & H, V+1 Lk 1
3951  else if(LinkNumber == 7)
3952  {
3953  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(4, TrackElement.HLoc - 1, TrackElement.VLoc, 9, RouteNumber))
3954  {
3955  if(AllRoutes->GetFixedRouteAt(211, RouteNumber).PrefDirSize() > 2)
3956  {
3957  // don't call for stub end routes
3959  LogActionErrorCalled = true;
3960  }
3961  AllRoutes->GetModifiableRouteAt(24, RouteNumber).ForceCancelRoute(7);
3962  }
3963  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(5, TrackElement.HLoc, TrackElement.VLoc + 1, 1, RouteNumber))
3964  // not else in case have different routes on each diagonal, though shouldn't be possible
3965  {
3966  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(212, RouteNumber).PrefDirSize() > 2)
3967  {
3968  // don't call for stub end routes
3970  }
3971  AllRoutes->GetModifiableRouteAt(25, RouteNumber).ForceCancelRoute(8);
3972  }
3973  }
3974 
3975  // for LinkNumber = 9, potentially fouled diagonals are at H+1, V, Lk 7 & H, V+1 Lk 3
3976  else if(LinkNumber == 9)
3977  {
3978  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(6, TrackElement.HLoc + 1, TrackElement.VLoc, 7, RouteNumber))
3979  {
3980  if(AllRoutes->GetFixedRouteAt(213, RouteNumber).PrefDirSize() > 2)
3981  {
3982  // don't call for stub end routes
3984  LogActionErrorCalled = true;
3985  }
3986  AllRoutes->GetModifiableRouteAt(26, RouteNumber).ForceCancelRoute(9);
3987  }
3988  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(7, TrackElement.HLoc, TrackElement.VLoc + 1, 3, RouteNumber))
3989  // not else in case have different routes on each diagonal, though shouldn't be possible
3990  {
3991  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(214, RouteNumber).PrefDirSize() > 2)
3992  {
3993  // don't call for stub end routes
3995  }
3996  AllRoutes->GetModifiableRouteAt(27, RouteNumber).ForceCancelRoute(10);
3997  }
3998  }
3999  }
4000  }
4001  Utilities->CallLogPop(698);
4002  return; // no route on other track or no other track
4003  }
4004  // here if there is a route at Element & EntryPos - so there can't be a route on the other track if a 4 track element, unless it's a bridge & that's ok
4005  for(unsigned int x = 0; x < AllRoutes->GetFixedRouteAt(155, RouteNumber).PrefDirSize(); x++)
4006  {
4007  RouteElement = AllRoutes->GetFixedRouteAt(156, RouteNumber).GetFixedPrefDirElementAt(165, x);
4008  bool PointsAtElement = (Track->TrackElementAt(987, Element).TrackType == Points); // new at v2.4.2 for points check - Xeon repoted it 30/05/20. He found that for routes that
4009  if(RouteElement.GetTrackVectorPosition() == (unsigned int)Element) // cross bridges at both levels can have entrypos 0 & other exitpos 2 so if don't have this check can cancel a route wrongly
4010  {
4011  if(RouteElement.GetELinkPos() == EntryPos)
4012  {
4013  Utilities->CallLogPop(699);
4014  return; // right direction
4015  }
4016  else if((RouteElement.GetELinkPos() == 2) && (EntryPos == 0) && PointsAtElement)
4017  {
4018  Utilities->CallLogPop(700);
4019  return; // right direction (points)
4020  }
4021  else if((RouteElement.GetELinkPos() == 0) && (EntryPos == 2) && PointsAtElement)
4022  {
4023  Utilities->CallLogPop(701);
4024  return; // right direction (points)
4025  }
4026  else if(RouteElement.GetXLinkPos() == EntryPos)
4027  {
4028  WrongRoute = true;
4029  break; // wrong direction
4030  }
4031  else if((RouteElement.GetXLinkPos() == 2) && (EntryPos == 0) && PointsAtElement) // ok for bridges
4032  {
4033  WrongRoute = true;
4034  break; // wrong direction
4035  }
4036  else if((RouteElement.GetXLinkPos() == 0) && (EntryPos == 2) && PointsAtElement) // ok for bridges
4037  {
4038  WrongRoute = true;
4039  break; // wrong direction
4040  }
4041  }
4042  }
4043  if(!WrongRoute)
4044  {
4045  throw Exception("Error, Element in route but no route found in CheckAndCancelRouteForWrongEndEntry");
4046  }
4047  if(AllRoutes->GetFixedRouteAt(180, RouteNumber).PrefDirSize() > 2)
4048  {
4049  // don't call for stub end routes
4051  }
4052  AllRoutes->GetModifiableRouteAt(14, RouteNumber).ForceCancelRoute(2);
4053  Utilities->CallLogPop(703);
4054 }
4055 
4056 // ---------------------------------------------------------------------------
4057 
4058 void TTrain::PlotTrainWithNewBackgroundColour(int Caller, TColor NewBackgroundColour, TDisplay *Disp)
4059 {
4060  if(BackgroundColour == NewBackgroundColour)
4061  {
4062  return; // don't replot if already correct
4063 
4064  }
4065  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainWithNewBackgroundColour," + AnsiString(NewBackgroundColour));
4066  bool ColourError = false, ColourError2 = false;
4067 
4068  RailGraphics->ChangeBackgroundColour(1, FrontCodePtr, FrontCodePtr, NewBackgroundColour, BackgroundColour, ColourError);
4069  if(ColourError)
4070  {
4071  ColourError2 = true;
4072  }
4073  for(int x = 0; x < 4; x++)
4074  {
4075  RailGraphics->ChangeBackgroundColour(2, HeadCodeGrPtr[x], HeadCodeGrPtr[x], NewBackgroundColour, BackgroundColour, ColourError);
4076  if(ColourError)
4077  {
4078  ColourError2 = true;
4079  }
4080  }
4081  if(ColourError2)
4082  {
4083  TrainController->StopTTClockMessage(63, "ERROR: Colour depth insufficient to display train colours properly. Please ensure that the 'safe' (web) palette of "
4084  "256 colours can be displayed");
4085  }
4086  // NB need a separate 'for' loop since the plot order can be different from the graphic order depending on the direction
4087  // of motion
4088  for(int x = 0; x < 4; x++)
4089  {
4090  PlotTrainGraphic(6, x, Disp);
4091  }
4092  BackgroundColour = NewBackgroundColour;
4093  Display->Update();
4094  // need to keep this since Update() not called for PlotSmallOutput as too slow
4095  Utilities->CallLogPop(704);
4096 }
4097 
4098 // ---------------------------------------------------------------------------
4099 
4100 void TTrain::SetTrainMovementValues(int Caller, int TrackVectorPosition, int EntryPos)
4101 /*
4102 Note: Within the loop BrakeRate can only increase and MaxExitSpeed can only reduce
4103 
4104 Summary: Called during PlotStartPosition to set initial values, when stopped and need to restart, and during UpdateTrain when Straddle is LeadMidLag,
4105 i.e. just as the front of a train is about to move fully onto an element, where TrackVectorPosition is the element immediately in front
4106 of the element the front of the train is moving fully on to. The function calculates the times and speeds at the next half-element and
4107 full-element moves.
4108 
4109 Detail: TrackVectorPosition & EntryPos correspond to the TrackVector element immediately in front of where the train is at
4110 the end of the current Update(). EntrySpeed is needed but this is a class data member so isn't passed in. Set the
4111 train BrakeRate to zero (for now, likely to be altered later), & check if zero entry speed with another train directly in front & if so
4112 remain stopped. Pick up the half length value and speed limit for the EntryPos track, and set FrontElementLength to the length of the
4113 EntryPos track, then set LimitingSpeed to the minimum of the element speed limit or the train's maximum speed. Check if running past a
4114 red signal and set SPADFlag if so (use 1 for EntrySpeed rather than 0 as this value is a double so could be slightly in excess of 0).
4115 In this case set the brake rate to maximum to stop as soon as possible.
4116 
4117 For no SPAD calculate the distance that will be travelled at the maximum speed at which the train can exit the next element at half
4118 MaxBrakeRate, this is DistanceAtHalfBraking (also calculate DistanceAtThreeQuarterBraking - used for stopping under signaller control).
4119 DistanceAtHalfBraking is used as the limiting forward look from the next element (i.e. following EntryPos)
4120 for computing the actual braking rate. If no more restrictive speed limits or reasons to stop are found within the forward look then the
4121 train can accelerate or stay at its (local) maximum speed for the next element. The maximum speed on exit from the next element is used
4122 for calculating the forward look because it represents the worst case - i.e. assumes that the train accelerates for the next element.
4123 
4124 A loop is now entered where the CumulativeLength is updated and each successive element (if there are any - current element checked
4125 first to see whether buffers or continuation) in turn is examined: first the length of the
4126 current element is added to the cumulative length; then the half length and speedlimit are set for the next element - points are
4127 followed according to their current setting (Attribute), but derailments are ignored as these are dealt with outside this function; checks
4128 are then made to see whether the next element is a red signal (train should stop before it); next element is a buffer (train should stop
4129 at the end of it so the cumulative length has the next element length added); current element is a buffer (train should stop
4130 at the end of the current element so no need to alter the cumulative length); or have reached a named location stop position. For any of
4131 these reasons, or if stopping under signaller control, there is no more looping, instead the braking rate is calculated to bring the train
4132 to a stop over CumulativeLength. For all normal purposes the braking rate will be less than half (light braking), or less than three
4133 quarters if stopping under signaller control (heavy braking). However if signals are reset in front of a train then the train may need
4134 emergency braking (> 90% max brake rate) and a SPAD may result. Similarly if points are chaged in front of a train that divert it into a
4135 siding then again emeregency braking may be necessary and a crash may result.
4136 
4137 If the train is due to stop then the function calculates the half and full times and speeds and returns. However the calculation depends
4138 on the conditions at entry. If the EntrySpeed is lower than MaxHalfSpeed and the EntryPos element is the one
4139 that the train has to stop at the end of, as it might be for example if train had been stopped at a signal and the next element is a
4140 buffer, then the train accelerates for half the element and brakes for the other half.
4141 Now the BrakeRate is calculated (limited to the MaxBrakeRate), but if it is less than a value calculated at an earlier pass round
4142 the loop then it retains its earlier value (may be due to a close speed restriction that requires more braking than a more distant stop
4143 requirement). The MaxExitSpeedAtHalfBraking (maximum speed at which the train can leave the current element and still stop when required
4144 at half the max braking rate) value is also calculated using EntrySpeed and CumulativeLength, but limiting it to the line speed limit or
4145 train MaxRunningSpeed whichever is the lower. If EntrySpeed > MaxExitSpeedAtHalfBraking then braking is required, so the half and full
4146 speed and time values for the current element are calculated using BrakeRate, EntrySpeed and CurrentElementHalfLength. If need to stop
4147 at the end of the current elemecumulativent for other than a red signal (SPADs can occur) then ExitSpeedFull is set to 0. It should be calculated
4148 as 0 anyway for other than a red signal but this makes sure. If EntrySpeed <= MaxExitSpeedAtHalfBraking then can calculate the half and
4149 full speed and time values for acceleration over the current element, but limit ExitSpeedHalf & Full to MaxExitSpeedAtHalfBraking or to
4150 the current element speed limit if necessary. Check whether ExitSpeedHalf <= EntrySpeed (+0.01 since it's a double) and use constant speed
4151 time values for Half & Full if so, but prior to this increase EntrySpeed if necessary to avoid a divide by zero error.
4152 
4153 If the train is not due to stop within the DistanceAtHalfBraking from the next element following EntryPos then the next element (if there
4154 is one) is checked to see if its speed limit is less than the current value of LimitingSpeed (which is the minimum of any earlier element's
4155 speed limit that has been examined within the loop and the train's MaxRunning speed), and if so LimitingSpeed is set down to it. Now
4156 the MaxExitSpeedAtHalfBraking is calculated, limiting it to LimitingSpeed if less, in case need to accelerate in the current element, in
4157 which case the exit speeds need to be limited to MaxExitSpeedAtHalfBraking. If EntrySpeed > LimitingSpeed then calculate the braking rate
4158 to bring the speed down to LimitingSpeed in CumulativeLength, keeping the existing BrakeRate value if lower and keeping it within
4159 MaxBrakeRate.
4160 
4161 Then, providing the current element isn't a buffer or continuation, the 'Current' values are updated from the 'Next' values ready for
4162 the next loop iteration. The loop is broken out of if the current element is a buffer or continuation, the next element is a
4163 continuation, or (CumulativeLength - FrontElementLength) >= DistanceAtHalfBraking.
4164 
4165 Now the final Half and Full values can be set for braking (if BrakeRate > 0.01), or accelerating - limiting the half and full exit speed
4166 values to MaxExitSpeedAtHalfBraking if necessary, and using constant speed time values if the exit speeds aren't much different to
4167 EntrySpeed and EntrySpeed > 0.01 (to avoid a divide by zero error).
4168 
4169 Note that in no circumstances will a train stop when straddling 3 elements, it will always be fully on two elements. This is ensured
4170 by UpdateTrain() which never sets any stop conditions unless the train is fully on 2 elements when that function returns, i.e. entered
4171 when Straddle == LeadMidLag
4172 */
4173 {
4174  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetTrainMovementValues," + AnsiString(TrackVectorPosition) + "," +
4175  AnsiString(EntryPos) + "," + HeadCode);
4176  int EntryHalfLength, CurrentElementHalfLength, NextElementHalfLength, CumulativeLength = 0, CurrentTrackVectorPosition = TrackVectorPosition;
4177  int DistanceAtHalfBraking, DistanceAtThreeQuarterBraking, ExitPos, NextTrackVectorPosition, NextEntryPos;
4178  bool RedSignalFlag = false, BuffersFlag = false, StationFlag = false, BuffersOrContinuationNowFlag = false, ContinuationNextFlag = false,
4179  TrainInFrontInSignallerModeFlag = false;
4180  double LimitingSpeed, FrontElementMaxSpeed, MaxExitSpeedAtHalfBrakingSquared, MaxExitSpeedAtHalfBraking, NextSpeedLimit, TempBrakeRate;
4181  double ExitSpeedHalfSquared, ExitSpeedFullSquared;
4182  bool SignallerStopRequired = false;
4183 
4185  // set high to begin with to avoid divide by zero errors on restart after stops, will be set lower later
4186 
4187  // Member variables: EntrySpeed, ExitSpeedHalf, ExitSpeedFull, MaxExitSpeed, BrakeRate, EntryTime, ExitTimeHalf, ExitTimeFull, FrontElementSpeedLimit, FrontElementLength;
4188 
4189  OneLengthAccelDecel = false;
4190  BrakeRate = 0;
4191  if(PowerAtRail < 1)
4192  {
4193  BrakeRate = CoastingBrakeRate; //brings train to a stop in 13km in 15min from starting speed of 100km/h (from research)
4194  }
4195 //find FrontElementLength & FrontElementSpeedLimit (these correspond to TrackVectorPosition input value);
4196  if(CurrentTrackVectorPosition > -1)
4197  {
4198  if(Track->TrackElementAt(855, CurrentTrackVectorPosition).TrackType == Points) // this test & section added at v0.6
4199  {
4200  if((EntryPos == 0) || (EntryPos == 2))
4201  {
4202  if(Track->TrackElementAt(856, CurrentTrackVectorPosition).Attribute == 0)
4203  {
4204  CurrentElementHalfLength = (Track->TrackElementAt(857, CurrentTrackVectorPosition).Length01) / 2;
4205  FrontElementSpeedLimit = Track->TrackElementAt(858, CurrentTrackVectorPosition).SpeedLimit01;
4206  }
4207  else
4208  {
4209  CurrentElementHalfLength = (Track->TrackElementAt(859, CurrentTrackVectorPosition).Length23) / 2;
4210  FrontElementSpeedLimit = Track->TrackElementAt(860, CurrentTrackVectorPosition).SpeedLimit23;
4211  }
4212  }
4213  else if(EntryPos == 1)
4214  {
4215  CurrentElementHalfLength = (Track->TrackElementAt(861, CurrentTrackVectorPosition).Length01) / 2;
4216  FrontElementSpeedLimit = Track->TrackElementAt(862, CurrentTrackVectorPosition).SpeedLimit01;
4217  }
4218  else // == 3
4219  {
4220  CurrentElementHalfLength = (Track->TrackElementAt(863, CurrentTrackVectorPosition).Length23) / 2;
4221  FrontElementSpeedLimit = Track->TrackElementAt(864, CurrentTrackVectorPosition).SpeedLimit23;
4222  }
4223  }
4224  else
4225  {
4226  if(EntryPos > 1)
4227  {
4228  CurrentElementHalfLength = (Track->TrackElementAt(348, CurrentTrackVectorPosition).Length23) / 2;
4229  FrontElementSpeedLimit = Track->TrackElementAt(349, CurrentTrackVectorPosition).SpeedLimit23;
4230  }
4231  else
4232  {
4233  CurrentElementHalfLength = (Track->TrackElementAt(350, CurrentTrackVectorPosition).Length01) / 2;
4234  FrontElementSpeedLimit = Track->TrackElementAt(351, CurrentTrackVectorPosition).SpeedLimit01;
4235  }
4236  }
4237  EntryHalfLength = CurrentElementHalfLength;
4238  FrontElementLength = 2 * CurrentElementHalfLength;
4239  }
4240  else
4241  {
4242  throw Exception("Error - CurrentTrackVectorPosition < 0 in SetTrainMovementValues");
4243  }
4244  if((CurrentElementHalfLength < 0) || (FrontElementSpeedLimit < 0))
4245  {
4246  throw Exception("Error - HalfLength or SpeedLimit < 0 in SetTrainMovementValues");
4247  }
4248  // check if zero entry speed with another train directly in front & if so remain stopped
4249  if((EntryPos > -1) && Track->OtherTrainOnTrack(2, CurrentTrackVectorPosition, EntryPos, TrainID) && (EntrySpeed < 1))
4250  {
4251  EntrySpeed = 0;
4252  ExitSpeedHalf = 0;
4253  ExitSpeedFull = 0;
4254  MaxExitSpeed = 0;
4255  BrakeRate = 0;
4256  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
4257  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
4258  StoppedForTrainInFront = true;
4259  TrainInFront = true;
4260  Utilities->CallLogPop(705);
4261  return;
4262  }
4263  // new at v2.4.0 - check for stopped and zero power
4264  if((EntrySpeed < 1) && PowerAtRail < 1)
4265  {
4266  EntrySpeed = 0;
4267  ExitSpeedHalf = 0;
4268  ExitSpeedFull = 0;
4269  MaxExitSpeed = 0;
4270  BrakeRate = 0;
4271  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
4272  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
4273  StoppedWithoutPower = true;
4274  Utilities->CallLogPop(2125);
4275  return;
4276  }
4277 //set LimitingSpeed & FrontElementMaxSpeed (internal values)
4278  if(BeingCalledOn)
4279  {
4280  LimitingSpeed = CallOnMaxSpeed;
4281  }
4282  else
4283  {
4284  LimitingSpeed = MaximumSpeedLimit;
4285  }
4286  if(LimitingSpeed > FrontElementSpeedLimit)
4287  {
4288  LimitingSpeed = FrontElementSpeedLimit;
4289  }
4290  if(LimitingSpeed > MaxRunningSpeed) //MaxRunningSpeed is set in AddTrain depending on timetable or signaller control mode
4291  {
4292  LimitingSpeed = MaxRunningSpeed;
4293  }
4294  FrontElementMaxSpeed = LimitingSpeed;
4295 
4296 /*
4297  for braking the deceleration rate is constant so the following formuli (Newton's Laws) are used:-
4298  (1) V^2/(3.6^2) = U^2/(3.6^2) - 2FS;
4299  (2) V/3.6 = U/3.6 - FT;
4300  (3) S = UT/3.6 - 0.5FT^2
4301  where(V = final speed in kph [km/h/3.6 = m/s], U = initial speed in km/h, F = deceleration rate in m/s/s, S = distance in m & T = time in secs)
4302 
4303  for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4304  (4) V^2/(3.6^2) - U^2/(3.6^2) = A^2T;
4305  (5) V = 3.6 * ((1.5*S*A^2) + U^3/ (3.6)^3)^0.333334;
4306  where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph , S = distance in m & T = time in secs
4307  It's a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4308 
4309  calc max speed that can attain on exit from next element (as could accelerate over next element) and use that speed to calc
4310  DistanceAtHalfBraking, if use actual speed may miss a stop requirement just outside look-ahead & accelerate, and at next calc
4311  be unable to stop or have hard acceleration followed immediately by hard braking, this speed makes for smoother operation
4312 */
4313 
4314 // check if running past a red signal without permission
4315  if((Track->TrackElementAt(352, CurrentTrackVectorPosition).Config[Track->GetNonPointsOppositeLinkPos(EntryPos)] == Signal) &&
4316  (Track->TrackElementAt(353, CurrentTrackVectorPosition).Attribute == 0) && (EntrySpeed > 1) && !AllowedToPassRedSignal &&
4317  !Track->TrackElementAt(1553, CurrentTrackVectorPosition).CallingOnSet)
4318  { //CallingOnSet added at v2.14.0
4319  SPADFlag = true; // user has to intervene to reset & restart after spad
4320  }
4321  if(!SPADFlag)
4322  {
4323  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4324  // to begin with calc the maximum exit speed (assumes accelerating) and then reduce it if necessary
4325  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4326  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/(3.6^3))^0.333334;
4327  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph, S = distance & T = time, note that km/h/3.6 = m/s
4328  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4329 
4330  double ExitSpeedAtMaxBraking;
4331  // below introduced at v2.4.0, was ExitSpeedFull = LimitingSpeed; but that allowed very high brake rates when
4332  // took signaller control of a fast failed train with signaller limiting speed 30km/h
4334  {
4335  ExitSpeedAtMaxBraking = 0;
4336  }
4337  else
4338  {
4339  ExitSpeedAtMaxBraking = sqrt((EntrySpeed * EntrySpeed) - 2 * MaxBrakeRate * FrontElementLength);
4340  }
4341  double SpeedToUse;
4342  // use the highest of LimitingSpeed or ExitSpeedAtMaxBraking - added at v2.4.2 because trains entering at a continuation with zero (or very low) speed
4343  // & 2 elements before signal caused ExitSpeedAtMaxBraking & hence DistanceAtHalfBraking and DistanceAtThreeQuarterBraking to be zero, so no restriction was recognised
4344  // for first element & train accelerated at maximum rate, then at 2nd element train couldn't brake in time and overran the signal - notified by Micke via Discord on 02/06/20
4345  if(ExitSpeedAtMaxBraking > LimitingSpeed)
4346  {
4347  SpeedToUse = ExitSpeedAtMaxBraking;
4348  }
4349  else
4350  {
4351  SpeedToUse = LimitingSpeed;
4352  }
4353  if(ExitSpeedFull > SpeedToUse)
4354  {
4355  ExitSpeedFull = SpeedToUse;
4356  }
4357  DistanceAtHalfBraking = ExitSpeedFull * ExitSpeedFull / 3.6 / 3.6 / MaxBrakeRate;
4358  DistanceAtThreeQuarterBraking = ExitSpeedFull * ExitSpeedFull / 3.6 / 3.6 / 1.5 / MaxBrakeRate; // used for signaller stops
4359 
4360  //now enter a do loop to examine each element in turn from the front of the train to calc the cumulative length and to see if a stop is required (flag set if so -
4361  //RedSignalFlag, BuffersFlag, StationFlag, TrainInFrontInSignallerModeFlag, SignallerStopRequired, StepForwardFlag) in which case there are no more loops
4362  // break out of the loop when ((CumulativeLength - FrontElementLength) < DistanceAtHalfBraking ) && ((!BuffersOrContinuationNowFlag && !ContinuationNextFlag) || SignallerStoppingFlag);
4363 
4364  do
4365  {
4366  RedSignalFlag = false;
4367  BuffersFlag = false;
4368  StationFlag = false;
4369  BuffersOrContinuationNowFlag = false;
4370  ContinuationNextFlag = false;
4371  // have to reset this after the above test
4372  // add current element length to CumulativeLength
4373  CumulativeLength += (2 * CurrentElementHalfLength);
4374  if((CumulativeLength >= DistanceAtThreeQuarterBraking) && (TrainMode == Signaller) && SignallerStoppingFlag)
4375  {
4376  SignallerStopRequired = true;
4377  // once set stays set until SignallerStoppingFlag reset, providing !BuffersOrContinuationNowFlag,
4378  // set SignallerStopBrakeRate to stop in CumulativeLength unless already higher (i.e. can only increase)
4379  double TempBR = EntrySpeed * EntrySpeed / 2 / 3.6 / 3.6 / CumulativeLength;
4380  if(SignallerStopBrakeRate < TempBR)
4381  {
4382  SignallerStopBrakeRate = TempBR;
4383  }
4384  }
4385  // first check for stops within the length of the current element, where don't want any more checks & don't want
4386  // to add in any extra to the CumulativeLength. Only applies for buffers & station stops as signals should have been caught
4387  // during the last loop when the NextTrackVectorPosition was the signal.
4388 
4389  // check if current element is a buffer
4390  if(Track->TrackElementAt(374, CurrentTrackVectorPosition).TrackType == Buffers)
4391  {
4392  // no need to add in the length of this element to CumulativeLength as already included
4393  BuffersFlag = true;
4394  }
4395  // check if current element is a station stop
4396  if(TrainMode == Timetable)
4397  {
4398  bool StopRequired = false;
4399  if(!TimetableFinished && (NameInTimetableBeforeCDT(12, Track->TrackElementAt(375, CurrentTrackVectorPosition).ActiveTrackElementName,
4400  StopRequired) > -1) && ((Track->TrackElementAt(376, CurrentTrackVectorPosition).StationEntryStopLinkPos1 == EntryPos) ||
4401  (Track->TrackElementAt(377, CurrentTrackVectorPosition).StationEntryStopLinkPos2 == EntryPos) ||
4402  (Track->TrackElementAt(1642, CurrentTrackVectorPosition).StationEntryStopLinkPos3 == EntryPos) ||
4403  (Track->TrackElementAt(1643, CurrentTrackVectorPosition).StationEntryStopLinkPos4 == EntryPos)))
4404  {
4405  // no need to add in the length of element to CumulativeLength
4406  if(StopRequired)
4407  {
4408  StationFlag = true;
4409  }
4410  }
4411  }
4412  else
4413  {
4414  StationFlag = false;
4415  }
4416  // set NextHalfLength & NextSpeedLimit, but only if current element not buffers or exit continuation - no next element for them
4417  if(((Track->TrackElementAt(354, CurrentTrackVectorPosition).TrackType == Buffers) || (Track->TrackElementAt(355,
4418  CurrentTrackVectorPosition).TrackType == Continuation)) && (EntryPos == 1))
4419  {
4420  BuffersOrContinuationNowFlag = true;
4421  }
4422  if(!BuffersOrContinuationNowFlag && !BuffersFlag && !StationFlag) // skip if buffers or station flags already set
4423  {
4424  if(Track->TrackElementAt(356, CurrentTrackVectorPosition).TrackType == Points)
4425  {
4426  if((EntryPos == 0) || (EntryPos == 2))
4427  {
4428  if(Track->TrackElementAt(357, CurrentTrackVectorPosition).Attribute == 0)
4429  {
4430  ExitPos = 1;
4431  }
4432  else
4433  {
4434  ExitPos = 3;
4435  }
4436  }
4437  else
4438  {
4439  ExitPos = 0;
4440  }
4441  }
4442  else
4443  {
4444  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
4445  }
4446  NextTrackVectorPosition = Track->TrackElementAt(358, CurrentTrackVectorPosition).Conn[ExitPos];
4447  NextEntryPos = Track->TrackElementAt(359, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
4448  if(NextTrackVectorPosition > -1)
4449  {
4450  if(Track->TrackElementAt(845, NextTrackVectorPosition).TrackType == Points)
4451  // this test & section added at v0.6
4452  {
4453  if((NextEntryPos == 0) || (NextEntryPos == 2))
4454  {
4455  if(Track->TrackElementAt(846, NextTrackVectorPosition).Attribute == 0)
4456  {
4457  NextElementHalfLength = (Track->TrackElementAt(847, NextTrackVectorPosition).Length01) / 2;
4458  NextSpeedLimit = Track->TrackElementAt(848, NextTrackVectorPosition).SpeedLimit01;
4459  }
4460  else
4461  {
4462  NextElementHalfLength = (Track->TrackElementAt(849, NextTrackVectorPosition).Length23) / 2;
4463  NextSpeedLimit = Track->TrackElementAt(850, NextTrackVectorPosition).SpeedLimit23;
4464  }
4465  }
4466  else if(NextEntryPos == 1)
4467  {
4468  NextElementHalfLength = (Track->TrackElementAt(851, NextTrackVectorPosition).Length01) / 2;
4469  NextSpeedLimit = Track->TrackElementAt(852, NextTrackVectorPosition).SpeedLimit01;
4470  }
4471  else // == 3
4472  {
4473  NextElementHalfLength = (Track->TrackElementAt(853, NextTrackVectorPosition).Length23) / 2;
4474  NextSpeedLimit = Track->TrackElementAt(854, NextTrackVectorPosition).SpeedLimit23;
4475  }
4476  }
4477  else
4478  {
4479  if(NextEntryPos > 1)
4480  {
4481  NextElementHalfLength = (Track->TrackElementAt(360, NextTrackVectorPosition).Length23) / 2;
4482  NextSpeedLimit = Track->TrackElementAt(361, NextTrackVectorPosition).SpeedLimit23;
4483  }
4484  else
4485  {
4486  NextElementHalfLength = (Track->TrackElementAt(362, NextTrackVectorPosition).Length01) / 2;
4487  NextSpeedLimit = Track->TrackElementAt(363, NextTrackVectorPosition).SpeedLimit01;
4488  }
4489  }
4490  }
4491  else
4492  {
4493  throw Exception("Error - Trying to access NextTrackVectorPosition when none present in SetTrainMovementValues");
4494  }
4495  // now check for stops, first cover those where don't want to add in length of next element
4496  // check if next element is a red signal - Attr 0,
4497  // note that this doesn't apply to trains stopped at a red signal since the signal position is
4498  // CurrentTrackVectorPosition not NextTrackVectorPosition
4499  bool StopRequired;
4500  if(Track->TrackElementAt(364, NextTrackVectorPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal)
4501  {
4502  if(Track->TrackElementAt(365, NextTrackVectorPosition).Attribute == 0)
4503  {
4504  // no need to add in the length of element to CumulativeLength
4505  RedSignalFlag = true;
4506  }
4507  // next element is a red signal
4508  }
4509  // check if current element is a station & next element contains a train - trains will always stop without crashing at a
4510  // station they are due to stop at even if there is a train in front blocking the normal stop position - providing there is
4511  // at least one platform element free
4513  CurrentTrackVectorPosition).ActiveTrackElementName, StopRequired) > -1) && Track->OtherTrainOnTrack(3, NextTrackVectorPosition, NextEntryPos, TrainID))
4514  {
4515  // no need to add in the length of element to CumulativeLength
4516  if(StopRequired)
4517  {
4518  StationFlag = true;
4519  }
4520  }
4521  // check if next element contains a train & in Signaller mode (always stops for train in front if in signaller mode)
4522  else if((TrainMode == Signaller) && Track->OtherTrainOnTrack(4, NextTrackVectorPosition, NextEntryPos, TrainID))
4523  // (Track->TrackElementAt(651, NextTrackVectorPosition).TrainIDOnElement > -1))
4524  {
4525  // no need to add in the length of element to CumulativeLength
4526  TrainInFrontInSignallerModeFlag = true;
4527  }
4528  // check if next element is a buffer, but if StepForwardFlag true then need to stop before reach the buffers
4529  else if((Track->TrackElementAt(366, NextTrackVectorPosition).TrackType == Buffers) && !StepForwardFlag)
4530  {
4531  // need to add in the length of that element to CumulativeLength
4532  CumulativeLength += Track->TrackElementAt(367, NextTrackVectorPosition).Length01;
4533  BuffersFlag = true;
4534  }
4535  // check if next element is a station stop
4537  NextTrackVectorPosition).ActiveTrackElementName, StopRequired) > -1) && ((Track->TrackElementAt(371,
4538  NextTrackVectorPosition).StationEntryStopLinkPos1 == EntryPos) || (Track->TrackElementAt(372,
4539  NextTrackVectorPosition).StationEntryStopLinkPos2 == EntryPos) || (Track->TrackElementAt(1644,
4540  NextTrackVectorPosition).StationEntryStopLinkPos3 == EntryPos) || (Track->TrackElementAt(1645,
4541  NextTrackVectorPosition).StationEntryStopLinkPos4 == EntryPos)))
4542  { // need to add in the length of that element to CumulativeLength if a stop required
4543  if(StopRequired)
4544  {
4545  StationFlag = true;
4546  CumulativeLength += Track->TrackElementAt(373, NextTrackVectorPosition).Length01;
4547  }
4548  }
4549  }
4550  //now can decide whether need to stop over CumulativeLength
4551  if(RedSignalFlag || BuffersFlag || StationFlag || TrainInFrontInSignallerModeFlag || SignallerStopRequired || StepForwardFlag) // no more loops
4552  {
4553  // have to come to a stop over CumulativeLength
4554  if(CumulativeLength == FrontElementLength)
4555  // will be if StepForwardFlag (if stopped to begin with on zero power then earlier check will intercept it and it won't reach here
4556  // only one length to go before stop so check whether need to accelerate for half length then brake for latter
4557  // half; calc speed at halfway point that corresponds to half braking rate for latter half of track element,
4558  // and if less than EntrySpeed then skip this section (don't need any acceleration)
4559  // if not calc speed at halfway point & if less than above set half speed to this value;
4560  // use constant acceleration in calculating half time point
4561  {
4562  MaxExitSpeed = 0;
4563  double MaxHalfSpeed;
4564  double MaxHalfSpeedAtHalfBraking = 3.6 * sqrt(MaxBrakeRate * FrontElementLength / 2);
4565  // have to halve the element length, & can't be zero or negative so no need to test
4566  // if(MaxHalfSpeedAtHalfBraking > LimitingSpeed) MaxHalfSpeed = LimitingSpeed; else MaxHalfSpeed = MaxHalfSpeedAtHalfBraking;
4567  if(MaxHalfSpeedAtHalfBraking > FrontElementMaxSpeed)
4568  {
4569  MaxHalfSpeed = FrontElementMaxSpeed;
4570  }
4571  else
4572  {
4573  MaxHalfSpeed = MaxHalfSpeedAtHalfBraking;
4574  }
4575  if(MaxHalfSpeed > (2 * EntrySpeed) && (PowerAtRail > 1)) //PowerAtRail condition added at v2.18.0
4576  // use 2x to prevent kangarooing at last element when had
4577  // been braking smoothly at less that 50% braking rate, 2x should prevent all but extreme cases
4578  {
4579  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)),
4580  0.333334);
4581  bool HalfSpeedLimited = false;
4582  if(MaxHalfSpeed < ExitSpeedHalf)
4583  {
4584  ExitSpeedHalf = MaxHalfSpeed;
4585  HalfSpeedLimited = true;
4586  }
4587  if(PowerAtRail > 1)
4588  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4589  {
4590  // [km/h/3.6 = m/s]
4591  ExitTimeHalf =
4592  EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue) / 86400.0);
4593  }
4594  else
4595  {
4596  ExitTimeHalf = EntryTime + TDateTime(EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4597  }
4598  // the above is the time taken to accelerate to ExitSpeedHalf, so if this is reached before the half
4599  // way point (i.e. HalfSpeedLimited is set) then the time will be too short; change later to equal the
4600  // braking time; not fully accurate but better to be equal than have a short acceleration period followed
4601  // by a long braking period
4602  ExitSpeedFull = 0;
4603  TempBrakeRate = ExitSpeedHalf * ExitSpeedHalf / 2 / 3.6 / 3.6 / EntryHalfLength;
4604  if(TempBrakeRate > MaxBrakeRate)
4605  {
4606  TempBrakeRate = MaxBrakeRate;
4607  }
4608  // shouldn't be but leave in anyway
4609  if(TempBrakeRate > BrakeRate)
4610  {
4611  BrakeRate = TempBrakeRate;
4612  }
4613  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4614  ExitTimeFull = ExitTimeHalf + TDateTime(ExitSpeedHalf / 3.6 / BrakeRate / 86400.0);
4615  if(HalfSpeedLimited)
4616  // this is the change referred to above
4617  {
4618  TDateTime BrakingTime = ExitTimeFull - ExitTimeHalf;
4619  ExitTimeHalf = EntryTime + BrakingTime;
4620  ExitTimeFull = ExitTimeHalf + BrakingTime;
4621  }
4622  OneLengthAccelDecel = true; //used in TrackTrainFloat in InterfaceUnit.cpp to show accelerating for first half move then decelerating
4623  Utilities->CallLogPop(1095);
4624  return;
4625  }
4626  }
4627  // set braking to achieve speed = 0 @ CumulativeLength up to MaxBrakeRate
4628  // calc MaxExitSpeed for element at EntryPosition & set to this or existing val if lower,
4629  // calc th, tf, sh, & sf
4630  TempBrakeRate = EntrySpeed * EntrySpeed / 2 / 3.6 / 3.6 / CumulativeLength;
4631  if(TempBrakeRate > MaxBrakeRate)
4632  {
4633  TempBrakeRate = MaxBrakeRate;
4634  }
4635  if(TempBrakeRate > BrakeRate)
4636  {
4637  BrakeRate = TempBrakeRate;
4638  }
4639  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4640  if(SignallerStopRequired)
4641  // set BrakeRate to max of its calculated value or SignallerStopBrakeRate
4642  {
4644  {
4646  // this prevents the brakerate from reducing for a signaller stop
4647  // regardless of other conditions that may change as progress round the loop
4648  }
4649  }
4651  // prevents BrakeRate dropping below SignallerStopBrakeRate once it's been set whether or not SignallerStopRequired set
4652  // SignallerStopRequired may not be set if a red signal found in a later calc, & brakerate may then drop
4653  {
4655  }
4656  int TempMaxExitSpeed;
4657  // calc current value & if less than MaxExitSpeed set that to this
4658  MaxExitSpeedAtHalfBrakingSquared = 3.6 * 3.6 * MaxBrakeRate * (CumulativeLength - FrontElementLength);
4659  if(MaxExitSpeedAtHalfBrakingSquared < 10)
4660  {
4661  MaxExitSpeedAtHalfBraking = 0;
4662  }
4663  else
4664  {
4665  MaxExitSpeedAtHalfBraking = sqrt(MaxExitSpeedAtHalfBrakingSquared);
4666  }
4667  // if(MaxExitSpeedAtHalfBraking > LimitingSpeed) MaxExitSpeed = LimitingSpeed; else MaxExitSpeed = MaxExitSpeedAtHalfBraking;
4668  // I think the above was dropped because it could cause MaxExitSpeed to increase (MaxExitSpeed is an external variable retained between loops)
4669  if(MaxExitSpeedAtHalfBraking > FrontElementMaxSpeed)
4670  {
4671  TempMaxExitSpeed = FrontElementMaxSpeed;
4672  }
4673  else
4674  {
4675  TempMaxExitSpeed = MaxExitSpeedAtHalfBraking;
4676  }
4677  if(TempMaxExitSpeed < MaxExitSpeed)
4678  {
4679  MaxExitSpeed = TempMaxExitSpeed;
4680  }
4681  // here have EntrySpeed & MaxExitSpeed (for the next element), BrakeRate (to bring speed to zero over
4682  // Cumulativelength, and Cumulativelength
4683 
4684  if((EntrySpeed > MaxExitSpeed) || SignallerStopRequired || (SignallerStopBrakeRate > 0.01)) // need to brake
4685  {
4686  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4687  if(ExitSpeedHalfSquared < 10)
4688  {
4689  ExitSpeedHalf = 0;
4690  }
4691  else
4692  {
4693  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4694  }
4695  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4696  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4697  if(ExitSpeedFullSquared < 10)
4698  {
4699  ExitSpeedFull = 0;
4700  }
4701  else
4702  {
4703  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4704  }
4705  if((StationFlag) && (CumulativeLength == FrontElementLength))
4706  {
4707  ExitSpeedFull = 0;
4708  // force a stop for station (not for buffers or red signal)
4709  }
4710  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4711  }
4712  // new condition at v2.4.0
4713  else if(PowerAtRail < 1)
4714  // use EntrySpeed, CumulativeLength & BrakeRate to calculate the half and full exit times and speeds for next element
4715  // avoid using AValue in denominator or have excessively long times
4716  {
4717  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * BrakeRate * FrontElementLength);
4718  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4719  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / (3.6 * BrakeRate * 86400.0));
4720 
4721  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * FrontElementLength);
4722  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4723  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / (3.6 * BrakeRate * 86400.0));
4724  }
4725  else // e.g. moving towards a signal or station after a speed limit, so can accelerate unless no power
4726  // without the power need above condition or have hours of delay times, above added at v2.4.0
4727  {
4728  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4729  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/(3.6^3))^0.333334;
4730  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph, S = distance & T = time, note that km/h/3.6 = m/s
4731  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4732  BrakeRate = 0;
4733  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)),
4734  0.333334);
4735  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4736  // above valid for ExitSpeedHalf & Full <= MaxExitSpeed
4738  // can accelerate continually over the half length
4739  {
4740  ExitTimeHalf = EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4741  / 86400.0);
4743  // can accelerate continually over the full length
4744  // i.e both (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull <= MaxExitSpeed)
4745  {
4746  ExitTimeFull =
4747  EntryTime + TDateTime(((ExitSpeedFull * ExitSpeedFull) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue) / 86400.0);
4748  }
4749  else // (ExitSpeedHalf <= MaxExitSpeed) but (ExitSpeedFull > MaxExitSpeed)
4750  // accelerate to MaxExitSpeed then reamin at this speed for rest of element
4751  {
4752  // added at v0.6 as a safeguard
4753  if(MaxExitSpeed < EntrySpeed)
4754  {
4756  }
4757  // to prevent DeltaExitTimeToMaxInSecs being negative
4758  if(MaxExitSpeed < 1)
4759  {
4760  MaxExitSpeed = 1;
4761  }
4762  // to prevent divide by zero error
4763  // below as was
4765  double DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4766  double DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4767  (1.5 * AValue * AValue);
4768  double RemainingDistance = double(FrontElementLength) - DistanceToMax;
4769  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4770  ExitTimeFull = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4771  }
4772  }
4773  else // ExitSpeedHalf > MaxExitSpeed, so ExitSpeedFull must also be > MaxExitSpeed
4774  // accelerate over first half to MaxExitSpeed then remain at this speed for rest of the first and
4775  // second halves of the element
4776  {
4777  // added at v0.6 as a safeguard
4778  if(MaxExitSpeed < EntrySpeed)
4779  {
4781  }
4782  // to prevent DeltaExitTimeToMaxInSecs being negative
4783  if(MaxExitSpeed < 1)
4784  {
4785  MaxExitSpeed = 1; // to prevent divide by zero error
4786  }
4787  // below as was
4789  double DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4790  double DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4791  (1.5 * AValue * AValue);
4792  double RemainingDistance = double(FrontElementLength / 2) - DistanceToMax;
4793  // remaining distance to half length
4794  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4795  ExitTimeHalf = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4797  ExitTimeFull = ExitTimeHalf + TDateTime(3.6 * EntryHalfLength / MaxExitSpeed / 86400.0);
4798  }
4799  }
4800  Utilities->CallLogPop(706);
4801  return;
4802  }
4803  else
4804  {
4805  if(!BuffersOrContinuationNowFlag)
4806  {
4807  if(NextSpeedLimit < LimitingSpeed)
4808  {
4809  LimitingSpeed = NextSpeedLimit;
4810  }
4811  }
4812  // calc max exit speed at half braking to ensure don't accelerate past it (if acceleration is required)
4813  int TempMaxExitSpeed;
4814  // calc current value & if less than MaxExitSpeed set that to this
4815  // Note that LimitingSpeed is the max value at the end of CumulativeLength, so MaxExitSpeedAtHalfBrakingSquared will be larger
4816  MaxExitSpeedAtHalfBrakingSquared = (LimitingSpeed * LimitingSpeed) + (3.6 * 3.6 * MaxBrakeRate * (CumulativeLength - FrontElementLength));
4817  if(MaxExitSpeedAtHalfBrakingSquared < 10)
4818  {
4819  MaxExitSpeedAtHalfBraking = 0;
4820  }
4821  else
4822  {
4823  MaxExitSpeedAtHalfBraking = sqrt(MaxExitSpeedAtHalfBrakingSquared);
4824  }
4825  if(MaxExitSpeedAtHalfBraking > FrontElementMaxSpeed)
4826  {
4827  TempMaxExitSpeed = FrontElementMaxSpeed;
4828  }
4829  else
4830  {
4831  TempMaxExitSpeed = MaxExitSpeedAtHalfBraking;
4832  }
4833  if(TempMaxExitSpeed < MaxExitSpeed)
4834  {
4835  MaxExitSpeed = TempMaxExitSpeed;
4836  }
4837  // MaxExitSpeed is an external variable & this can reduce its value
4838  if(EntrySpeed > LimitingSpeed)
4839  // note that LimitingSpeed is more restrictive than MaxExitSpeed
4840  // calc TempBrakeRate & set BrakeRate to this or keep existing val if higher
4841  {
4842  TempBrakeRate = ((EntrySpeed * EntrySpeed) - (LimitingSpeed * LimitingSpeed)) / 3.6 / 3.6 / 2 / CumulativeLength;
4843  if(TempBrakeRate > MaxBrakeRate)
4844  {
4845  TempBrakeRate = MaxBrakeRate;
4846  }
4847  // shouldn't be for speedlimits since all known in advance, but include anyway
4848  if(TempBrakeRate > BrakeRate)
4849  {
4850  BrakeRate = TempBrakeRate;
4851  }
4852  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4853  }
4854  }
4855  if(!BuffersOrContinuationNowFlag)
4856  {
4857  CurrentTrackVectorPosition = NextTrackVectorPosition;
4858  EntryPos = NextEntryPos;
4859  CurrentElementHalfLength = NextElementHalfLength;
4860  if(Track->TrackElementAt(378, NextTrackVectorPosition).TrackType == Continuation)
4861  {
4862  ContinuationNextFlag = true;
4863  }
4864  }
4865  }
4866  while(((CumulativeLength - FrontElementLength) < DistanceAtHalfBraking) && ((!BuffersOrContinuationNowFlag && !ContinuationNextFlag) ||
4868  // check from the end of the front element, if include the front element and could brake during it, then will skip further loops
4869  // & miss a stop requirement just beyond the front element. happened in Richard Standing's railway where a new service introduced
4870  // on a 100m length, with 20m length after & then a red signal - train accelerated over the 100m then caused a SPAD as too short a
4871  // stopping distance after it.
4872 
4873  //(!BuffersOrContinuationNowFlag && !ContinuationNextFlag) true when no continuation on either the next element and next but one element.
4874  //There won't be a buffer on the next element or would have caught earlier, just using this flag for convenience.
4875 
4876  // If SignallerStoppingFlag then don't exit loop because of an imminent continuation, because continuation
4877  // not immediately in front (if it is then LeadElement will be the continuation & SignallerStoppingFlag will be reset in UpdateTrain()),
4878  // need to at least give a chance to stop on signaller command, if keep moving until continuation is immediately in front then will
4879  // exit loop & that is OK as don't want to stop so close to a continuation, if that happens it means that the command to stop was given
4880  // too late
4881 
4882  // set final braking or acc'n speed & time values
4883  if(BrakeRate > 0.01)
4884  {
4885  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4886  if(ExitSpeedHalfSquared < 10)
4887  {
4888  ExitSpeedHalf = 0;
4889  }
4890  else
4891  {
4892  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4893  }
4894  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4895  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4896  if(ExitSpeedFullSquared < 10)
4897  {
4898  ExitSpeedFull = 0;
4899  }
4900  else
4901  {
4902  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4903  }
4904  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4905  }
4906  else
4907  {
4908  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4909  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/ (3.6)^3)^0.333334;
4910  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph , S = distance in m & T = time
4911  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4912 
4913  BrakeRate = 0;
4914  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4915  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4916  // above valid for ExitSpeedHalf & Full <= MaxExitSpeed
4918  {
4919  if(PowerAtRail > 1)
4920  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4921  {
4922  // [km/h/3.6 = m/s]
4923  ExitTimeHalf = EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4924  / 86400.0);
4925  }
4926  else
4927  {
4928  ExitTimeHalf = EntryTime + TDateTime(EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4929  }
4931  // (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull <= MaxExitSpeed)
4932  {
4933  if(PowerAtRail > 1)
4934  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4935  {
4936  // [km/h/3.6 = m/s]
4937  ExitTimeFull = EntryTime + TDateTime(((ExitSpeedFull * ExitSpeedFull) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4938  / 86400.0);
4939  }
4940  else
4941  {
4942  ExitTimeFull = EntryTime + TDateTime(2 * EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4943  }
4944  }
4945  else // (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull > MaxExitSpeed)
4946  {
4947  // added at v0.6 as a safeguard
4948  if(MaxExitSpeed < EntrySpeed)
4949  {
4951  }
4952  // to prevent DeltaExitTimeToMaxInSecs being negative
4953  if(MaxExitSpeed < 1)
4954  {
4955  MaxExitSpeed = 1; // to prevent divide by zero error
4956  }
4957  // below as was
4959  double DeltaExitTimeToMaxInSecs;
4960  double DistanceToMax;
4961  if(PowerAtRail > 1) // added at v2.4.0
4962  {
4963  DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4964  DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4965  (1.5 * AValue * AValue);
4966  }
4967  else // shouldn't ever get here given that ExitSpeedFull > ExitSpeedHalf
4968  {
4969  DeltaExitTimeToMaxInSecs = 2 * EntryHalfLength * 3.6 / EntrySpeed;
4970  // these not really accurate but will be good enough
4971  DistanceToMax = EntryHalfLength;
4972  }
4973  double RemainingDistance = double(FrontElementLength) - DistanceToMax;
4974  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4975  ExitTimeFull = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4976  }
4977  }
4978  else // ExitSpeedHalf > MaxExitSpeed, ExitSpeedFull must be > MaxExitSpeed
4979  {
4980  // added at v0.6 as a safeguard
4981  if(MaxExitSpeed < EntrySpeed)
4982  {
4984  }
4985  // to prevent DeltaExitTimeToMaxInSecs being negative
4986  if(MaxExitSpeed < 1)
4987  {
4988  MaxExitSpeed = 1; // to prevent divide by zero error
4989  }
4990  // below as was
4992  double DeltaExitTimeToMaxInSecs;
4993  double DistanceToMax;
4994  if(PowerAtRail > 1) // added at v2.4.0
4995  {
4996  DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4997  DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4998  (1.5 * AValue * AValue);
4999  }
5000  else
5001  {
5002  DeltaExitTimeToMaxInSecs = 2 * EntryHalfLength * 3.6 / EntrySpeed;
5003  // these not really accurate but will be good enough
5004  DistanceToMax = EntryHalfLength / 2;
5005  }
5006  double RemainingDistance = double(FrontElementLength / 2) - DistanceToMax; // remaining distance to half length
5007  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
5008  ExitTimeHalf = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
5010  ExitTimeFull = ExitTimeHalf + TDateTime(3.6 * EntryHalfLength / MaxExitSpeed / 86400.0);
5011  }
5012  }
5013  }
5014 
5015  else // SPADFlag set
5016  {
5018  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
5019  if(ExitSpeedHalfSquared < 10)
5020  {
5021  ExitSpeedHalf = 0;
5022  }
5023  else
5024  {
5025  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
5026  }
5027  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
5028  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
5029  if(ExitSpeedFullSquared < 10)
5030  {
5031  ExitSpeedFull = 0;
5032  }
5033  else
5034  {
5035  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
5036  }
5037  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
5038 
5039  // check if the exit speed is < 80% of the stopping speed for the next element, and if so stop at end of this element
5040  // this is because would stop short of end of next element (in reality the time to reach the end of the next element
5041  // would be too short (could be so short as to make the train jump) as time is calculated purely on speed & brake rate);
5042  // 80% is used as the brake rate might be set to come to a halt at the end of the next element in which case the speed
5043  // will be the stopping speed.
5044  if(ExitSpeedFull > 0)
5045  {
5046  if(Track->TrackElementAt(746, CurrentTrackVectorPosition).TrackType == Points)
5047  {
5048  if((EntryPos == 0) || (EntryPos == 2))
5049  {
5050  if(Track->TrackElementAt(747, CurrentTrackVectorPosition).Attribute == 0)
5051  {
5052  ExitPos = 1;
5053  }
5054  else
5055  {
5056  ExitPos = 3;
5057  }
5058  }
5059  else
5060  {
5061  ExitPos = 0;
5062  }
5063  }
5064  else
5065  {
5066  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5067  }
5068  NextTrackVectorPosition = Track->TrackElementAt(748, CurrentTrackVectorPosition).Conn[ExitPos];
5069  NextEntryPos = Track->TrackElementAt(749, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
5070  if(NextTrackVectorPosition > -1) // not a continuation or buffer
5071  {
5072  int NextElementLength;
5073  if(NextEntryPos > 1)
5074  {
5075  NextElementLength = (Track->TrackElementAt(750, NextTrackVectorPosition).Length23);
5076  }
5077  else
5078  {
5079  NextElementLength = (Track->TrackElementAt(751, NextTrackVectorPosition).Length01);
5080  }
5081  double NextStoppingSpeed = sqrt(3.6 * 3.6 * 2 * BrakeRate * NextElementLength);
5082  if(ExitSpeedFull < (0.8 * NextStoppingSpeed))
5083  {
5084  ExitSpeedFull = 0;
5085  }
5086  }
5087  }
5088  }
5089  // allow all values to be set normally in case need to brake, then test for zero power & need to coast
5090  if(PowerAtRail < 1) // new at v2.4.0 note that km/h/3.6 = m/s
5091  {
5092  // bring to a stop in 20 elements at 100km/h & assume each 100m long for calculating exit times but if on a continuation maintain speed <--NO,
5093  //change to BrakeRate = CoastingBrakeRate = 0.03 and calc times etc as normal - because of Albie Vowles' error report of 231223 where noticed
5094  //that failed train treated track lengths of > 2km as 100m so very noticeable. Keep going for exiting at continuation.
5095 
5096  //Coasting deceleration rate from paper 'Real-time train motion parameter estimation using an Unscented Kalman Filter' at
5097  //'https://www.sciencedirect.com/science/article/pii/S0968090X22002212'. In particular Fig 6 in section 4.3 shows coasting from 400sec to
5098  //1000sec corresponds to speed drop from 140km/h to 80km/h, i.e. 60km/h in 600sec, equivalent to 0.02777m/s/s deceleration, so use 0.03m/s/s.
5099 
5100  if(LeadElement > -1)
5101  {
5103  // don't stop on a continuation either entering or leaving
5104  {
5107  MaxExitSpeed = LimitingSpeed;
5108  BrakeRate = 0;
5109  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
5110  // assume length is 50m for ease of calc
5111  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
5112  Utilities->CallLogPop(2126);
5113  return;
5114  }
5115  }
5116  else if(MidElement > -1)
5117  {
5119  {
5122  MaxExitSpeed = LimitingSpeed;
5123  BrakeRate = 0;
5124  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
5125  // assume length is 50m for ease of calc
5126  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
5127  Utilities->CallLogPop(2127);
5128  return;
5129  }
5130  }
5131  else if(LagElement > -1)
5132  {
5134  {
5137  MaxExitSpeed = LimitingSpeed;
5138  BrakeRate = 0;
5139  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
5140  // assume length is 50m for ease of calc
5141  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
5142  Utilities->CallLogPop(2128);
5143  return;
5144  }
5145  }
5146 /* dropped at v2.18.1 in favour of CoastingBrakeRate which = 0.03m/s/s = see above explanation
5147  if(EntrySpeed > 7.5) // keep going for at least another element
5148  {
5149  ExitSpeedHalf = EntrySpeed - 2.5;
5150  ExitSpeedFull = EntrySpeed - 5;
5151  MaxExitSpeed = LimitingSpeed;
5152  BrakeRate = 0;
5153  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed - 1.25) / 86400.0));
5154  // assume length is 50m for ease of calc
5155  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf - 1.25) / 86400.0));
5156  Utilities->CallLogPop(2129);
5157  return;
5158  }
5159  else // stop immediately, don't enter next element, floating label had displayed last exit speed full on 2nd half move so with zero when fully on element
5160  {
5161  // will appear to have slowed at steady rate
5162  ExitSpeedHalf = 0;
5163  ExitSpeedFull = 0;
5164  MaxExitSpeed = LimitingSpeed;
5165  BrakeRate = 0;
5166  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
5167  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
5168  StoppedWithoutPower = true;
5169  Utilities->CallLogPop(2130);
5170  return;
5171  }
5172 */
5173  }
5174  // TempBrakeRate=MinSingle; TempBrakeRate=MaxSingle; TempBrakeRate=MinDouble; TempBrakeRate=MaxDouble;//included to stop warnings from unused declarations in math.hpp
5175  // TempBrakeRate=MinExtended; TempBrakeRate=MaxExtended; TempBrakeRate=MinComp; TempBrakeRate=MaxComp;//included to stop warnings from unused declarations in math.hpp
5176  Utilities->CallLogPop(707);
5177 }
5178 // ---------------------------------------------------------------------------
5179 /*
5180  bool TTrain::IsTerminalStation(int TrackVectorPosition, int EntryPos)
5181  {
5182  int NextExitPos;
5183  TTrackElement NextElement = Track->TrackElementAt(379, TrackVectorPosition), TempElement;
5184  if(TimetableVector.empty()) return false;
5185  if(NextElement.ActiveTrackElementName != TimetableVector.begin()->LocationName) return false;
5186  while((NextElement.ActiveTrackElementName == TimetableVector.begin()->LocationName) && (NextElement.TrackType != Buffers))
5187  {
5188  //check for points & follow attribute, but don't worry about a derail as that dealt with elsewhere
5189  if((NextElement.TrackType != Points) || ((EntryPos != 0) && (EntryPos != 2)))
5190  {
5191  NextExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5192  }
5193  else if((NextElement.TrackType == Points) && ((EntryPos == 0) || (EntryPos == 2)))
5194  {
5195  if(NextElement.Attribute == 0) NextExitPos = 1; else NextExitPos = 3;
5196  }
5197  TempElement = Track->TrackElementAt(380, NextElement.Conn[NextExitPos]);//need temp as NextElement used in next step
5198  NextElement = TempElement;
5199  }
5200  if(NextElement.ActiveTrackElementName != TimetableVector.begin()->LocationName) return false;
5201  if(NextElement.TrackType == Buffers) return true;
5202  return false;//shouldn't reach here but include to prevent compiler return warning
5203  }
5204 */
5205 // ---------------------------------------------------------------------------
5206 
5207 int TTrain::NameInTimetableBeforeCDT(int Caller, AnsiString Name, bool &Stop)
5208 /*
5209  returns the number by which the train ActionVectorEntryPtr needs
5210  to be incremented to point to the location arrival entry or passtime entry before a change of direction. Used to display missed
5211  actions when a stop or pass location has been reached before other timetabled events have been carried out. If can't find it, or Name
5212  is "", -1 is returned. A change of direction is the limit of the search because a train may not stop at a location on the way out
5213  but stop on way back, and in these circumstances no actions have been missed. Stop indicates whether the train will stop at (true)
5214  or pass (false) the location.
5215 */{
5216  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NameInTimetableBeforeCDT," + Name + "," + HeadCode);
5217  Stop = false;
5218  if(TimetableFinished || (Name == ""))
5219  {
5220  Utilities->CallLogPop(957);
5221  return(-1);
5222  }
5224 /*added the following check at v2.11.1 because of a fault found in Kevin Smith's railway (notified 02/01/22). CH01 started at
5225 Chester behind the stop position, but when it departed and this function was called it found Chester again with no cdt
5226 before it (it went round the Liverpool Loop), so it stopped again when it reached the stop position and reported all intermediate stops as having been
5227 missed. This check is for the train just having departed from the station in question, and if it has then any further stations
5228 with the same name are ignored - i.e. it stops a train from stopping at the same station twice in succession.
5229 */
5230  if(Ptr > &TrainDataEntryPtr->ActionVector.at(0))
5231  {
5232  Ptr--;
5233  if((Ptr->DepartureTime > TDateTime(-1)) && (Ptr->LocationName == Name))
5234  {
5235  if((Ptr->FormatType == TimeLoc) || (Ptr->FormatType == TimeTimeLoc))
5236  {
5237  Utilities->CallLogPop(2444);
5238  return(-1);
5239  }
5240  }
5241  }
5242  // start looking from current pointer position
5243  for(TActionVectorEntry *Ptr = ActionVectorEntryPtr; Ptr < &TrainDataEntryPtr->ActionVector.back(); Ptr++)
5244  {
5245  if((Ptr->Command == "cdt") || (Ptr->FormatType == Repeat))
5246  {
5247  break;
5248  }
5249  if((Ptr->ArrivalTime > TDateTime(-1)) && (Ptr->LocationName == Name))
5250  {
5251  if((Ptr->FormatType == TimeLoc) || (Ptr->FormatType == TimeTimeLoc))
5252  {
5253  Stop = true;
5254  Utilities->CallLogPop(960);
5255  return (Ptr - ActionVectorEntryPtr);
5256  }
5257  }
5258  if((Ptr->EventTime > TDateTime(-1)) && (Ptr->LocationName == Name) && (Ptr->Command == "pas"))
5259  {
5260  Utilities->CallLogPop(1517);
5261  return (Ptr - ActionVectorEntryPtr);
5262  }
5263  }
5264  Utilities->CallLogPop(959);
5265  return(-1); // not found a valid entry
5266 }
5267 
5268 // ---------------------------------------------------------------------------
5269 
5271 /* Checks forward from train LeadElement, following leading point attributes but ignoring trailing point attributes,
5272  until finds either a train or a signal/buffers/continuation/loop. If finds a train returns false, else returns true.
5273  Ignores the call-on signal.
5274 */{
5275  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ClearToNextSignal" + "," + HeadCode);
5276  int ReturnVal = 0;
5277  int ElementCount = 0;
5278 /* dropped at v2.12.0 as takes up a great deal of time unnecessarily - substitute 1000 elements instead and return true (very unlikely to need to search this far [10km at min length])
5279  for(unsigned int x = 0; x < Track->TrackVector.size(); x++)
5280  {
5281  Track->TrackElementAt(1031, x).TempTrackMarker01 = false;
5282  Track->TrackElementAt(1032, x).TempTrackMarker23 = false;
5283  }
5284 */
5285  int CurrentTrackVectorPosition = LeadElement, NextTrackVectorPosition;
5286  int EntryPos = LeadEntryPos, ExitPos, NextEntryPos;
5287 
5288  while(true)
5289  {
5290  if((Track->TrackElementAt(382, CurrentTrackVectorPosition).TrainIDOnElement > -1) && (Track->TrackElementAt(383,
5291  CurrentTrackVectorPosition).TrainIDOnElement != TrainID))
5292  {
5293  ReturnVal = 1;
5294  break;
5295  }
5296  if(((Track->TrackElementAt(384, CurrentTrackVectorPosition).TrackType == Buffers) || (Track->TrackElementAt(385,
5297  CurrentTrackVectorPosition).TrackType == Continuation)) && (EntryPos == 1))
5298  {
5299  ReturnVal = 2;
5300  break;
5301  }
5302  if((EntryPos < 2) && (Track->TrackElementAt(386, CurrentTrackVectorPosition).Config[1 - EntryPos] == Signal) && !Track->TrackElementAt(529,
5303  CurrentTrackVectorPosition).CallingOnSet && (LeadElement != CurrentTrackVectorPosition)) // CallingOnSet true when position lights lit,
5304  {//added LeadElement condition at v2.18.0 as train may be on the callon signal after CallOnSet false & don't want to return true for that
5305  ReturnVal = 3;
5306  break;
5307  }
5308 /* not needed at and after v2.12.0, see above
5309  if((Track->TrackElementAt(387, CurrentTrackVectorPosition).TrackType == Bridge) || (Track->TrackElementAt(388, CurrentTrackVectorPosition).TrackType == Crossover))
5310  {
5311  if((EntryPos < 2) && (Track->TrackElementAt(523, CurrentTrackVectorPosition).TempTrackMarker01))
5312  // must be a loop - reached same point as examined earlier
5313  {
5314  ReturnVal = 4;
5315  break;
5316  }
5317  else if((EntryPos > 1) && (Track->TrackElementAt(524, CurrentTrackVectorPosition).TempTrackMarker23))
5318  {
5319  ReturnVal = 4;
5320  break;
5321  }
5322  }
5323  else
5324  {
5325  if((Track->TrackElementAt(525, CurrentTrackVectorPosition).TempTrackMarker01) || (Track->TrackElementAt(526, CurrentTrackVectorPosition).TempTrackMarker23))
5326  {
5327  ReturnVal = 4;
5328  break;
5329  }
5330  }
5331  if(EntryPos < 2)
5332  {
5333  Track->TrackElementAt(389, CurrentTrackVectorPosition).TempTrackMarker01 = true;
5334  }
5335  else
5336  {
5337  Track->TrackElementAt(527, CurrentTrackVectorPosition).TempTrackMarker23 = true;
5338  }
5339 */
5340 
5341  if(Track->TrackElementAt(390, CurrentTrackVectorPosition).TrackType == Points)
5342  {
5343  if((EntryPos == 0) || (EntryPos == 2))
5344  {
5345  if(Track->TrackElementAt(391, CurrentTrackVectorPosition).Attribute == 0)
5346  {
5347  ExitPos = 1;
5348  }
5349  else
5350  {
5351  ExitPos = 3;
5352  }
5353  }
5354  else
5355  {
5356  ExitPos = 0;
5357  }
5358  }
5359  else
5360  {
5361  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5362  }
5363  NextTrackVectorPosition = Track->TrackElementAt(392, CurrentTrackVectorPosition).Conn[ExitPos];
5364  NextEntryPos = Track->TrackElementAt(393, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
5365  CurrentTrackVectorPosition = NextTrackVectorPosition;
5366  EntryPos = NextEntryPos;
5367  ElementCount++;
5368  if(ElementCount > 1000)
5369  {
5370  ReturnVal = 4;
5371  break;
5372  }
5373  }
5374  if(ReturnVal == 1)
5375  {
5376  Utilities->CallLogPop(708);
5377  return(false);
5378  }
5379  if(ReturnVal == 2)
5380  {
5381  Utilities->CallLogPop(709);
5382  return(true);
5383  }
5384  if(ReturnVal == 3)
5385  {
5386  Utilities->CallLogPop(946);
5387  return(true);
5388  }
5389  if(ReturnVal == 4)
5390  {
5391  Utilities->CallLogPop(947);
5392  return(true);
5393  }
5394  else
5395  {
5396  throw Exception("Error - failed to set ReturnVal in ClearToNextSignal()");
5397  }
5398 }
5399 
5400 // ---------------------------------------------------------------------------
5401 
5403 /*
5404  Check whether calling-on conditions met - a) approaching train has stopped at a signal but not at a location;
5405  b) if there is a facing train at the station, it is being held appropriately (must be awaiting a join (Fjo or jbo) or a
5406  change of direction (cdt), remaining here (Frh), or under signaller control);
5407  c) at least 1 platform available for the approaching train; d) points (if any) set for direct route into platform;
5408  e) approaching train is to stop at station; f) no more facing signals between train and platform; g) [dropped g]
5409  h) train in front preventing route being set far enough to release stop signal; i) train in front not exiting at continuation; j) signal must be within 4km of
5410  the stop platform; k) [dropped (k), now can set a reoute or part route into platform so can set points more easily.] l) no existing route conflicts with the route into the platform,
5411  m) not failed or stopped without power
5412  If all OK & route or part route not already set then set an unrestricted route into the station (just to the first platform), to prevent point changing or other route conflicts - if a partial route set than can still
5413  change points outside the route or have a route conflict if another route is set.
5414 */{
5415  if(Track->RouteFlashFlag || TrainFailed || StoppedWithoutPower) //failed & no power conditions added at v2.10.0
5416  {
5417  return(false); // don't want to create a new route from the stop signal if one is already in construction & can't call on if failed or no power
5418  }
5419  // some of the callingon route elements may be involved
5420  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CallingOnAllowed" + "," + HeadCode);
5421  bool PlatformFoundFlag = false, StopRequired = false, SkipRouteCheck = false, RouteOrPartRouteSet = false; // last added at v1.2.0
5422  int CurrentTrackVectorPosition = LeadElement, NextTrackVectorPosition, ElementNumber = 0, Distance = 0;
5423  int RouteStartPosition;
5424  // this is the track vector position of the start element for the new unrestricted route - one past the stop signal
5425  int PlatformPosition;
5426  // the track vector position of the first stop platfrom
5427  int EntryPos = LeadEntryPos, ExitPos, NextEntryPos, RouteID;
5428  // not used here
5429  AnsiString LeadStationName = Track->TrackElementAt(395, LeadElement).ActiveTrackElementName; // still OK even if ""
5430  int LeadElementDistance = Track->TrackElementAt(1017, LeadElement).Length01; //added after 2.7.0 as don't want to add this to overall distance since train has already covered this distance
5431  // use Length01, may be wrong for points/crossovers/bridges but unlikely to occur in practice
5432  // must be stopped at a signal but not at a location & still in timetable (a)
5434  // no need to check for SignallerStopped as this function only called in Timetable mode
5435  {
5436  Utilities->CallLogPop(711);
5437  return(false);
5438  }
5439  while(true)
5440  {
5441  TTrackElement &CurrentTrackElement = Track->TrackElementAt(396, CurrentTrackVectorPosition);
5442  // don't look further than 4km ahead (j)
5443  if(Distance > (4000 + LeadElementDistance))
5444  {
5445  Utilities->CallLogPop(967);
5446  return(false);
5447  }
5448  // if find another train on an element in front, before find a valid platform, return false (c)
5449  if((CurrentTrackElement.TrainIDOnElement > -1) && (CurrentTrackElement.TrainIDOnElement != TrainID) && !PlatformFoundFlag)
5450  {
5451  Utilities->CallLogPop(713);
5452  return(false);
5453  }
5454  // if find another train in front when there is a valid platform (keep searching after find a platform as train may still
5455  // be facing later on)
5456  if((CurrentTrackElement.TrainIDOnElement > -1) && (CurrentTrackElement.TrainIDOnElement != TrainID) && PlatformFoundFlag) //<-can return true in error if other train on bridge
5457  { //on other track, but leave in as shouldn't cause
5458  // get LeadElement, if -1 return (could be exiting at continuation) (i) //any problems - see if anyone reports it and if
5459  TTrain OtherTrain = TrainController->TrainVectorAtIdent(12, CurrentTrackElement.TrainIDOnElement); //so try to correct
5460  if(OtherTrain.LeadElement == -1)
5461  {
5462  Utilities->CallLogPop(714);
5463  return(false);
5464  }
5465  // if a facing train then make sure it is awaiting a join (Fjo or jbo) or a change of direction (cdt), or remaining here (Frh) (b)
5466  if(OtherTrain.LeadElement == CurrentTrackVectorPosition)
5467  {
5468  AnsiString OtherCommand = OtherTrain.ActionVectorEntryPtr->Command;
5469  if((OtherCommand == "Fjo") || (OtherCommand == "jbo") || (OtherCommand == "cdt") || (OtherCommand == "Frh") ||
5470  (OtherTrain.TrainMode == Signaller))
5471  {
5472  break;
5473  }
5474  else
5475  {
5476  Utilities->CallLogPop(955);
5477  return(false);
5478  }
5479  }
5480  else // (h)
5481  {
5482  break;
5483  }
5484  }
5485  // if reach buffers or exit continuation return false (can set route)
5486  if(((CurrentTrackElement.TrackType == Buffers) || (CurrentTrackElement.TrackType == Continuation)) && (EntryPos == 1))
5487  {
5488  Utilities->CallLogPop(716);
5489  return(false);
5490  }
5491  // if reach forward signal (other than the one the train is waiting at) return false (can set route) (f)
5492  if((EntryPos < 2) && (CurrentTrackElement.Config[1 - EntryPos] == Signal) && (CurrentTrackVectorPosition != Track->TrackElementAt(404,
5494  {
5495  Utilities->CallLogPop(717);
5496  return(false);
5497  }
5498  // if reach a location that isn't in timetable return false - drop this as still can't set a route
5499 /*
5500  if((Track->TrackElementAt(405, CurrentTrackVectorPosition).ActiveTrackElementName != "") && (Track->TrackElementAt(406, CurrentTrackVectorPosition).ActiveTrackElementName != LeadStationName) &&
5501  (NameInTimetableBeforeCDT(14, Track->TrackElementAt(407, CurrentTrackVectorPosition).ActiveTrackElementName) == -1))
5502  {
5503  Utilities->CallLogPop(718);
5504  return false;
5505  }
5506 */
5507  // if reach a location that is in timetable set PlatformFoundFlag (but not if position is points set to diverge) (e)
5508  if((CurrentTrackElement.ActiveTrackElementName != "") && (CurrentTrackElement.ActiveTrackElementName != LeadStationName) &&
5509  (NameInTimetableBeforeCDT(15, CurrentTrackElement.ActiveTrackElementName, StopRequired) > -1))
5510  {
5511  if(StopRequired)
5512  {
5513  if((CurrentTrackElement.TrackType != Points) || ((CurrentTrackElement.TrackType == Points) && (CurrentTrackElement.Attribute == 0)))
5514  {
5515  if(!PlatformFoundFlag)
5516  {
5517  PlatformPosition = CurrentTrackVectorPosition;
5518  }
5519  // ensure this only set once at first valid platform position - the unrestricted route will end here
5520  PlatformFoundFlag = true;
5521  }
5522  }
5523  }
5524  // Drop this below - was to prevent call-on if front train had left the station. Criterion now is not that front
5525  // train has to be at station but that has to be before the next forward signal
5526 /*
5527  if((Track->TrackElementAt(411, CurrentTrackVectorPosition).ActiveTrackElementName == "") && (PlatformFoundFlag))
5528  {
5529  Utilities->CallLogPop(719);
5530  return false;
5531  }
5532 */
5533  // make sure points are followed correctly (d) & set ExitPos
5534  if(CurrentTrackElement.TrackType == Points)
5535  {
5536  if((EntryPos == 0) || (EntryPos == 2))
5537  {
5538  if(CurrentTrackElement.Attribute == 0)
5539  {
5540  ExitPos = 1;
5541  }
5542  else
5543  {
5544  ExitPos = 3;
5545  }
5546  }
5547  if(EntryPos == 1)
5548  {
5549  if(CurrentTrackElement.Attribute == 0)
5550  {
5551  ExitPos = 0;
5552  }
5553  else
5554  {
5555  Utilities->CallLogPop(720);
5556  return(false);
5557  }
5558  }
5559  if(EntryPos == 3)
5560  {
5561  if(CurrentTrackElement.Attribute == 1)
5562  {
5563  ExitPos = 0;
5564  }
5565  else
5566  {
5567  Utilities->CallLogPop(721);
5568  return(false);
5569  }
5570  }
5571  }
5572  else
5573  {
5574  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5575  }
5576  // check existing routes - if element forward of the signal (ElementNumber == 2) is AutoSignals then OK without further checks as this route must extend to
5577  // the next signal so must at least reach the station, also if have another route set (must be unrestricted) from either the stop signal or the element after it
5578  // to or towards the platform (& all points set correctly) then OK, otherwise reject if (1) there are any route elements already set from element
5579  // forward of element after the signal to & including the first platform element (covers crossover with other route set) or (2) a fouled diagonal (k)
5580  if(ElementNumber < 2)
5581  {
5582  SkipRouteCheck = true;
5583  }
5584  else
5585  {
5586  SkipRouteCheck = false;
5587  }
5588  if(ElementNumber == 1) // the stop signal
5589  {
5590  RouteStartPosition = CurrentTrackVectorPosition;
5591  }
5592 /*
5593  if(ElementNumber == 2)
5594  {
5595  if(AllRoutes->GetRouteTypeAndNumber(18, CurrentTrackVectorPosition, EntryPos, RouteID) == AllRoutes->AutoSigsRoute) AutoSigs = true;
5596  else AutoSigs = false;
5597  if(AllRoutes->GetRouteTypeAndNumber(25, CurrentTrackVectorPosition, EntryPos, RouteID) == AllRoutes->NotAutoSigsRoute) OtherFullRouteSet = true;
5598  }
5599 */
5600  if(ElementNumber > 1)
5601  {
5602  if(AllRoutes->GetRouteTypeAndNumber(26, CurrentTrackVectorPosition, EntryPos, RouteID) != AllRoutes->NoRoute)
5603  {
5604  RouteOrPartRouteSet = true;
5605  }
5606  else
5607  {
5608  RouteOrPartRouteSet = false;
5609  }
5610  }
5611  if(!SkipRouteCheck && !RouteOrPartRouteSet)
5612  {
5613  if(AllRoutes->TrackIsInARoute(16, CurrentTrackVectorPosition, EntryPos)) // must be a conflicting route
5614  {
5615  Utilities->CallLogPop(1859);
5616  return(false);
5617  }
5618  int ExitLink = CurrentTrackElement.Link[ExitPos];
5619  if((ExitLink == 1) || (ExitLink == 3) || (ExitLink == 7) || (ExitLink == 9))
5620  {
5621  if(AllRoutes->DiagonalFouledByRouteOrTrain(6, CurrentTrackElement.HLoc, CurrentTrackElement.VLoc, ExitLink))
5622  {
5623  Utilities->CallLogPop(1850);
5624  return(false);
5625  }
5626  }
5627  }
5628  // finished all checks, now update CurrentTrackVectorPosition & EntryPos for the next iteration
5629  if(EntryPos < 2)
5630  {
5631  Distance += CurrentTrackElement.Length01;
5632  }
5633  else
5634  {
5635  Distance += CurrentTrackElement.Length23;
5636  }
5637  NextTrackVectorPosition = CurrentTrackElement.Conn[ExitPos];
5638  NextEntryPos = CurrentTrackElement.ConnLinkPos[ExitPos];
5639  CurrentTrackVectorPosition = NextTrackVectorPosition;
5640  EntryPos = NextEntryPos;
5641  ElementNumber++;
5642  } // while(true)
5643 
5644  // if all OK & autosigs route not already set then set an unrestricted route into the station (just to the first platform)
5645  // from the stop signal (note that it may be last in an autosigs route)
5646  // a single element route at the stop signal should have been removed prior to this function being called (that called before
5647  // this in ClockTimer2)
5648 
5649  // now add elements to the CallonVector
5650  TAllRoutes::TCallonEntry CallonEntry(RouteOrPartRouteSet, RouteStartPosition, PlatformPosition);
5651 
5652  AllRoutes->CallonVector.push_back(CallonEntry);
5653  Utilities->CallLogPop(1860);
5654  return(true); // return false if fail to set route for any reason
5655 }
5656 
5657 // ---------------------------------------------------------------------------
5658 /*
5659  bool TTrain::TimetableFinished()
5660  {
5661  if((ActionVectorEntryPtr == TrainDataEntryPtr->ActionVector.end()) || (ActionVectorEntryPtr->FormatType == Repeat))//past all actions
5662  {
5663  return true;
5664  }
5665  return false;
5666  }
5667 */
5668 // ---------------------------------------------------------------------------
5669 
5670 AnsiString TTrain::GetTrainHeadCode(int Caller)
5671 
5672 {
5673  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetTrainHeadCode" + "," + HeadCode);
5674  AnsiString RepeatHeadCode = TrainController->GetRepeatHeadCode(0, HeadCode, RepeatNumber, IncrementalDigits);
5675 
5676  Utilities->CallLogPop(1452);
5677  return(RepeatHeadCode);
5678 }
5679 
5680 // ---------------------------------------------------------------------------
5681 
5682 TDateTime TTrain::GetTrainTime(int Caller, TDateTime Time)
5683 {
5684  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetTrainTime," + Utilities->Format96HHMMSS(Time));
5685  TDateTime RepeatTime = TrainController->GetRepeatTime(1, Time, RepeatNumber, IncrementalMinutes);
5686 
5687  Utilities->CallLogPop(1453);
5688  return(RepeatTime);
5689 }
5690 
5691 // ---------------------------------------------------------------------------
5692 
5693 bool TTrain::IsThereAnAdjacentTrain(int Caller, TTrain *&TrainToBeJoinedBy)
5694 {
5695  // Used to check for a stopped adjacent train for use in PopUp menu //new at v2.4.0
5696  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsThereAnAdjacentTrain" + "," + HeadCode);
5697  // check if there's a stopped adjacent train, if there is but not under sig control give a message in calling function
5698  // first check that train is fully on the railway
5699  bool FrontValid = false, RearValid = false;
5700  TTrackElement FrontAdjacentTrackElement, RearAdjacentTrackElement;
5701 
5702  if((LeadElement == -1) || (MidElement == -1))
5703  {
5704  TrainToBeJoinedBy = NULL;
5705  Utilities->CallLogPop(2131);
5706  return(false);
5707  }
5709  {
5710  FrontAdjacentTrackElement = Track->TrackElementAt(965, (Track->TrackElementAt(966, LeadElement).Conn[LeadExitPos]));
5711  FrontValid = true;
5712  }
5714  {
5715  RearAdjacentTrackElement = Track->TrackElementAt(968, (Track->TrackElementAt(969, MidElement).Conn[MidEntryPos]));
5716  RearValid = true;
5717  }
5718  int TrainToBeJoinedByID = -1;
5719 
5720  // first check if on a 2-track element & select correct ID number if so
5721  if(FrontValid)
5722  {
5723  if(FrontAdjacentTrackElement.TrackType == Bridge)
5724  {
5726  {
5727  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
5728  }
5729  else
5730  {
5731  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
5732  }
5733  }
5734  else
5735  {
5736  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnElement;
5737  }
5738  }
5739  if((TrainToBeJoinedByID < 0) && RearValid)
5740  {
5741  // first check if on a 2-track element & select correct ID number if so
5742  if(RearAdjacentTrackElement.TrackType == Bridge)
5743  {
5745  {
5746  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
5747  }
5748  else
5749  {
5750  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
5751  }
5752  }
5753  else
5754  {
5755  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnElement;
5756  }
5757  }
5758  if(TrainToBeJoinedByID < 0) // no adjacent train
5759  {
5760  TrainToBeJoinedBy = NULL;
5761  Utilities->CallLogPop(2132);
5762  return(false);
5763  }
5764  TrainToBeJoinedBy = &(TrainController->TrainVectorAtIdent(44, TrainToBeJoinedByID));
5765  if(!TrainToBeJoinedBy->Stopped())
5766  {
5767  TrainToBeJoinedBy = NULL;
5768  Utilities->CallLogPop(2133);
5769  return(false);
5770  }
5771  Utilities->CallLogPop(2134);
5772  return(true);
5773 }
5774 
5775 // ---------------------------------------------------------------------------
5776 
5777 void TTrain::LogAction(int Caller, AnsiString OwnHeadCode, AnsiString OtherHeadCode, TActionType ActionType, AnsiString LocationName, AnsiString SplitDistribution,
5778  TDateTime TimetableNonRepeatTime, bool Warning)
5779 /*
5780  Time = timetable time, the time adjustments for repeat trains is carried out internally
5781  Not all messages need this, if not needed a dummy value is required but not used
5782 
5783  Arrive: 06:05:40: 2F46 arrived at Old Street 1 minute late (Min. Dwell Time xxx)
5784  Pass: 06:05:40: 2F46 passed Old Street 1 minute late
5785  Terminate: 06:05:40: 2F46 terminated at Old Street 1 minute late <-- sent from RemainHere as LogAction not called for terminate
5786  //NB for Frh just give terminated message but without event time - don't use this function
5787  Depart: 06:05:15: 3F43 departed from Essex Road 2 minutes late
5788  Create: 06:05:40: 2F46 created at Old Street 1 minute late
5789  Enter: 06:05:40: 2F46 entered railway at Old Street 1 minute late
5790  Leave: 06:05:40: 2F46 left railway at 57-N4 1 minute late
5791  FrontSplit: 06:05:40: 2F46 split mass%-Power% = 10-50 from front to 3D54 at Old Street 1 minute late
5792  RearSplit: 06:05:40: 2F46 split mass%-Power% = 10-50 from rear to 3D54 at Old Street 1 minute late
5793  JoinedByOther: 06:05:40: 2F46 joined by 3D54 at Old Street 1 minute late
5794  ChangeDirection: 06:05:40: 2F46 changed direction at Old Street 1 minute late
5795  ChangeDescription: 06:05:40: 2F46 changed its description to 'NewDescription' at Old Street 1 minute late
5796  ChangeMaxSpeed: 06:05:40: 2F46 changed its maximum speed to 'NewMaxSpeed' at Old Street 1 minute late
5797  NewService: 06:05:40: 2F46 became new service 3D54 at Old Street 1 minute late
5798  TakeManualControl: 06:05:40: 2F46 taken under signaller control at Old Street
5799  RestoreTimetableControl: 06:05:40: 2F46 restored to timetable control at Old Street
5800  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY DUE TO CRASH at Old Street
5801  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY DUE TO DERAILMENT at Old Street
5802  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY at Old Street
5803  SignallerMoveForwards 06:05:40: 2F46 received signaller authority to proceed
5804  SignallerChangeDirection 06:05:40: 2F46 changed direction under signaller control at Old Street
5805  SignallerPassRedSignal 06:05:40: 2F46 received signaller authority to pass stop signal
5806  SignallerJoin 06:05:40: 2F46 joined under signaller control by 3D54 at Old Street //new at v2.4.0
5807  TrainFailure 06:05:40: 2F46 suffered an onboard power failure at Old Street //new at v2.4.0
5808  RepairFailedTrain 06:05:40: 2F46 failure repaired at Old Street //new at v2.4.0
5809  SignallerControlStop 06:05:40: 2F46 received signaller instruction to stop
5810  SignallerStop 06:05:40: 2F46 stopped on signaller command
5811  SignallerLeave: 06:05:40: 2F46 left railway under signaller control at 57-N4
5812  SignallerStepForward: 06:05:40: 2F46 received signaller authority to step forward
5813 */{
5814  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LogAction," + OwnHeadCode + "," + OtherHeadCode + "," +
5815  AnsiString(ActionType) + "," + LocationName + "," + HeadCode);
5816  AnsiString BaseLog = "", WarningBaseLog = "", ReminderBaseLog = "", PerfLog = "", ActionLog = "";
5817  int IntMinsLate = 0;
5818  bool TTEvent = false; //indicates a timetabled event, prevents reminders for non-tt events where these set for next tt event
5819  //don't need it for warnings as these passed in from appropriate calling functions
5820  // need to set it in case MinsLate == 0, since it isn't tested for that
5821  if(ActionType == Arrive)
5822  {
5823  ActionLog = " arrived at ";
5824  TTEvent = true;
5825  }
5826  if(ActionType == Terminate) //redundant as Logaction not called for terminate - RemainHere deals with logging for terminate
5827  {
5828  if(TerminatedMessageSent) // to avoid it being sent twice
5829  {
5830  Utilities->CallLogPop(1104);
5831  return;
5832  }
5833  ActionLog = " terminated at ";
5834  TTEvent = true;
5835  TerminatedMessageSent = true;
5836  }
5837  if(ActionType == Depart)
5838  {
5839  ActionLog = " departed from ";
5840  TTEvent = true;
5841  }
5842  if(ActionType == Pass)
5843  {
5844  ActionLog = " passed ";
5845  TTEvent = true;
5846  }
5847  if(ActionType == Create)
5848  {
5849  ActionLog = " created at ";
5850  }
5851  if(ActionType == Enter)
5852  {
5853  ActionLog = " entered railway at ";
5854  }
5855  if(ActionType == ChangeDescription)
5856  {
5857  ActionLog = " changed its description to '" + Description + "' at "; //change to train description at v2.16.1
5858  TTEvent = true;
5859  }
5860  if(ActionType == ChangeMaxSpeed)
5861  {
5862  ActionLog = " changed its maximum speed to " + ActionVectorEntryPtr->NewMaxSpeed + " at "; //change to train max speed at v2.21.0
5863  TTEvent = true;
5864  }
5865  if(ActionType == Leave)
5866  {
5867  ActionLog = " left railway at ";
5868  TTEvent = true;
5869  }
5870  if(ActionType == FrontSplit)
5871  {
5872  ActionLog = " split mass%-Power% = " + SplitDistribution + " from front to ";
5873  TTEvent = true;
5874  }
5875  if(ActionType == RearSplit)
5876  {
5877  ActionLog = " split mass%-Power% = " + SplitDistribution + " from rear to ";
5878  TTEvent = true;
5879  }
5880  if(ActionType == JoinedByOther)
5881  {
5882  ActionLog = " joined by ";
5883  TTEvent = true;
5884  }
5885  if(ActionType == ChangeDirection)
5886  {
5887  ActionLog = " changed direction at ";
5888  TTEvent = true;
5889  }
5890  if(ActionType == NewService)
5891  {
5892  ActionLog = " became new service ";
5893  TTEvent = true;
5894  }
5895  if(ActionType == TakeSignallerControl)
5896  {
5897  ActionLog = " taken under signaller control at ";
5898  }
5899  if(ActionType == RestoreTimetableControl)
5900  {
5901  ActionLog = " restored to timetable control at ";
5902  }
5903  if(ActionType == RemoveTrain)
5904  {
5905  if(Crashed)
5906  {
5907  ActionLog = " REMOVED FROM RAILWAY DUE TO CRASH at ";
5908  }
5909  else if(Derailed)
5910  {
5911  ActionLog = " REMOVED FROM RAILWAY DUE TO DERAILMENT at ";
5912  }
5913  else
5914  {
5915  ActionLog = " REMOVED FROM RAILWAY at ";
5916  }
5917  }
5918  if(ActionType == SignallerMoveForwards)
5919  {
5920  ActionLog = " received signaller authority to proceed";
5921  }
5922  if(ActionType == SignallerStepForward)
5923  {
5924  ActionLog = " received signaller authority to step forward";
5925  }
5926  if(ActionType == SignallerChangeDirection)
5927  {
5928  ActionLog = " changed direction under signaller control at ";
5929  }
5930  if(ActionType == SignallerPassRedSignal)
5931  {
5932  ActionLog = " received signaller authority to pass stop signal";
5933  }
5934  if(ActionType == SignallerControlStop)
5935  {
5936  ActionLog = " received signaller instruction to stop";
5937  }
5938  if(ActionType == SignallerStop)
5939  {
5940  ActionLog = " stopped on signaller instruction ";
5941  }
5942  if(ActionType == SignallerJoin)
5943  {
5944  ActionLog = " joined under signaller control by ";
5945  }
5946  if(ActionType == TrainFailure)
5947  {
5948  ActionLog = " suffered an onboard power failure at ";
5949  }
5950  if(ActionType == RepairFailedTrain)
5951  {
5952  ActionLog = " failure repaired at ";
5953  }
5954  if(ActionType == SignallerLeave)
5955  {
5956  ActionLog = " left railway under signaller control at ";
5957  }
5958  if(OtherHeadCode != "")
5959  {
5960  OtherHeadCode += " at ";
5961  }
5962  TDateTime ActualTime = TrainController->TTClockTime;
5963 
5964  if(Warning)
5965  {
5966  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " WARNING: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5967  WarningBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5968  }
5969  else //added at v2.19.0
5970  {
5971  if(TTEvent && ((ActionVectorEntryPtr->Reminder == 1) || (ActionVectorEntryPtr->Reminder == 4)))
5972  {
5973  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5974  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5975  }
5976  else if(TTEvent && (ActionVectorEntryPtr->Reminder == 2) && (ActionLog == " departed from ")) //depart only
5977  {
5978  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5979  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5980  }
5981  else if(TTEvent && (ActionVectorEntryPtr->Reminder == 3) && (ActionLog == " arrived at ")) //arrive only
5982  {
5983  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5984  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5985  }
5986  else
5987  {
5988  BaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5989  }
5990  }
5991 
5992  bool TimePerformance = true;
5993  AnsiString MinMinsString = "";
5994  if((ActionType == TakeSignallerControl) || (ActionType == RestoreTimetableControl) || (ActionType == RemoveTrain) || (ActionType == SignallerMoveForwards)
5995  || (ActionType == SignallerChangeDirection) || (ActionType == SignallerPassRedSignal) || (ActionType == SignallerControlStop) ||
5996  (ActionType == SignallerStop) || (ActionType == SignallerLeave) || (ActionType == SignallerStepForward) || (ActionType == SignallerJoin) ||
5997  (ActionType == TrainFailure) || (ActionType == RepairFailedTrain))
5998  // SignallerJoin & RepairFailedTrain new at v2.4.0
5999  {
6000  TimePerformance = false;
6001  }
6002  if(TimePerformance)
6003  {
6004  double MinsLate = ((double)(ActualTime - GetTrainTime(1, TimetableNonRepeatTime))) * 1440;
6005  MinsDelayed = float(MinsLate);
6006  if(ActionType == Pass) //added at v2.9.2 to prevent time to act increasing suddenly for early pass times then becoming 'NOW' when stops at signal
6007  {
6008  MinsDelayed = 0;
6009  }
6010  // new v2.2.0 for OpActionPanel, can be positive or negative
6011  if(ActionType == Arrive)
6012  {
6014  }
6015  // since train has just arrived this value is the
6016  // recoverable time available at this stop, so reduce MinsDelayed by this amount to prevent it being
6017  // subtracted from later stop recoverable times.
6018  if(MinsLate < 0)
6019  {
6020  IntMinsLate = int(ceil(MinsLate));
6021  }
6022  if(MinsLate > 0)
6023  {
6024  IntMinsLate = int(floor(MinsLate));
6025  }
6026  if(IntMinsLate == 0)
6027  {
6028  PerfLog = " on time";
6029  }
6030  else if(IntMinsLate == 1)
6031  {
6032  PerfLog = " 1 minute late";
6033  }
6034  else if(IntMinsLate == -1)
6035  {
6036  PerfLog = " 1 minute early";
6037  }
6038  else if(IntMinsLate > 1)
6039  {
6040  PerfLog = " " + AnsiString(IntMinsLate) + " minutes late";
6041  }
6042  else if(IntMinsLate < -1)
6043  {
6044  int PosIntMinsLate = -IntMinsLate;
6045  PerfLog = " " + AnsiString(PosIntMinsLate) + " minutes early";
6046  }
6047  if(LocationName.Pos('-') > 0)
6048  {
6049  PerfLog = "," + PerfLog;
6050  // if a position add a comma to separate vertical position number from number of minutes (better appearance)
6051  }
6052  if((ActionType == Arrive) && (ArrivalMinDwellTime > 30.1)) //add 0.1 to avoid rounding errors, can't use ArrivalMinDwellTime as not set until UpdateTrain
6053  {
6054  double MDTdouble = ArrivalMinDwellTime / 60;
6055  double MDT = int(MDTdouble * 10);
6056  MDT = MDT / 10;
6057  MinMinsString = "mins";
6058  if((MDT < 1.1) && (MDT > 0.9))
6059  {
6060  MinMinsString = "min";
6061  }
6062  PerfLogForm->PerformanceLog(0, BaseLog + PerfLog + " (Min. Dwell Time " + MDT + MinMinsString + ')');
6063  }
6064  else
6065  {
6066  PerfLogForm->PerformanceLog(68, BaseLog + PerfLog);
6067  }
6068  }
6069  else
6070  {
6071  PerfLogForm->PerformanceLog(1, BaseLog);
6072  }
6073  if(Warning)
6074  {
6075  Display->WarningLog(0, WarningBaseLog);
6076  }
6077  if(ReminderBaseLog != "") //added at v2.19.0
6078  {
6079  Display->WarningLog(24, ReminderBaseLog);
6080  ReminderBaseLog = ""; //reset to null
6081  ActionVectorEntryPtr->Reminder = 0; //to prevent reminders for repeats
6082  }
6083  // update statistics
6084  if((ActionType == Arrive) && (IntMinsLate == 0))
6085  {
6087  }
6088  else if((ActionType == Arrive) && (IntMinsLate > 0))
6089  {
6091  TrainController->TotLateArrMins += IntMinsLate;
6092  }
6093  else if((ActionType == Arrive) && (IntMinsLate < 0))
6094  {
6096  TrainController->TotEarlyArrMins += abs(IntMinsLate);
6097  }
6098 
6099  else if((ActionType == Pass) && (IntMinsLate == 0))
6100  {
6102  }
6103  else if((ActionType == Pass) && (IntMinsLate > 0))
6104  {
6106  TrainController->TotLatePassMins += IntMinsLate;
6107  }
6108  else if((ActionType == Pass) && (IntMinsLate < 0))
6109  {
6111  TrainController->TotEarlyPassMins += abs(IntMinsLate);
6112  }
6113 
6114  else if((ActionType == Leave) && (IntMinsLate == 0)) //new at v2.9.1 as had been omitted in error earlier
6115  {
6117  }
6118  else if((ActionType == Leave) && (IntMinsLate > 0))
6119  {
6121  TrainController->TotLateExitMins += IntMinsLate;
6122  }
6123  else if((ActionType == Leave) && (IntMinsLate < 0))
6124  {
6126  TrainController->TotEarlyExitMins += abs(IntMinsLate);
6127  }
6128 
6129  else if((ActionType == Depart) && (IntMinsLate == 0)) //can't depart early
6130  {
6132  }
6133  else if((ActionType == Depart) && (IntMinsLate > 0))
6134  {
6136  TrainController->TotLateDepMins += IntMinsLate;
6137  }
6138  Utilities->CallLogPop(968);
6139 }
6140 
6141 // ---------------------------------------------------------------------------
6142 
6143 void TTrain::TrainHasFailed(int Caller)
6144 {
6145  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainHasFailed," + HeadCode);
6146  if(Crashed || Derailed || DerailPending)
6147  {
6148  TrainFailurePending = false;
6149  Utilities->CallLogPop(2135);
6150  return;
6151  }
6152  AnsiString LocName = "";
6153 
6154  if(LeadElement > -1)
6155  {
6157  }
6158  if((LocName == "") && (MidElement > -1))
6159  {
6161  }
6162  if((LocName == "") && LeadElement > -1)
6163  {
6164  LocName = Track->TrackElementAt(974, LeadElement).ElementID;
6165  }
6166  if((LocName == "") && (MidElement > -1))
6167  {
6168  LocName = Track->TrackElementAt(975, MidElement).ElementID;
6169  }
6170  TrainController->StopTTClockMessage(81, HeadCode + " has suffered an onboard power failure at " + LocName);
6171  TrainFailed = true;
6172  TrainFailurePending = false;
6173  CallingOnFlag = false; //added at v2.10.0
6175  PowerAtRail = 0.08;
6176  AValue = sqrt(2 * PowerAtRail / Mass);
6178  // TrainFailed only called when PlotElements properly set to Lead, Mid & Lag elements
6179  if(Stopped())
6180  {
6181  EntrySpeed = 0;
6182  ExitSpeedHalf = 0;
6183  ExitSpeedFull = 0;
6184  MaxExitSpeed = 0;
6185  BrakeRate = 0;
6186  StoppedWithoutPower = true;
6187  }
6189  LogAction(33, HeadCode, "", TrainFailure, LocName, "", TDateTime(0), true);
6190  // true for warning, TDateTime not used
6191  Utilities->CallLogPop(2136);
6192 }
6193 
6194 // ---------------------------------------------------------------------------
6195 
6196 void TTrain::FrontTrainSplit(int Caller) //Major rewrite at v2.18.0 using new ThisLocationLongEnoughForSplit
6197 {
6198 /*
6199  Split logic for is:- the final 4 train positions must overlap with the original train, & final 4 positions
6200  will maximise the number at the location. Note that this function isn't sophisticated enough to account for trains already at the
6201  location in determining the 4 positions, and will give a failure message if a train obstructs any of the 4 positions. In these
6202  circumstances the other train will need to be moved sufficiently away to release all 4 positions, then the train will split.
6203 
6204  Note that for front split only the continuing service has the min dwell time as the front needs to go first & keeps to timetable is possible
6205 */
6206  TrainController->LogEvent("" + AnsiString(Caller) + ",FrontTrainSplit" + "," + HeadCode);
6207  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FrontTrainSplit" + "," + HeadCode);
6208 
6209 /* restriction removed at v2.19.0
6210  if(PowerAtRail < 1)
6211  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can split when power restored
6212  {
6213  if(!ZeroPowerNoFrontSplitMessage)
6214  {
6215  TrainController->StopTTClockMessage(82, HeadCode + ": A train without power can't split");
6216  }
6217  ZeroPowerNoFrontSplitMessage = true;
6218  Utilities->CallLogPop(2137);
6219  return;
6220  }
6221 */
6222 
6223  AnsiString LocationName = Track->TrackElementAt(555, LeadElement).ActiveTrackElementName;
6224 
6225  if(LocationName == "")
6226  {
6227  LocationName = Track->TrackElementAt(837, MidElement).ActiveTrackElementName;
6228  }
6229  int RearTrainRearPos, RearTrainFrontPos, RearTrainExitPos;
6230  int FrontTrainRearPos, FrontTrainFrontPos;
6232 
6233  if(LocationName == "")
6234  {
6235  throw Exception("Error - LocationName not set in FrontTrainSplit");
6236  }
6237  // if message given call at ~5 sec intervals in case train repositioned
6238 
6239  bool TemporaryDelay = false;
6241  MidEntryPos, FrontTrainFrontPos, FrontTrainRearPos, RearTrainFrontPos, RearTrainRearPos, TemporaryDelay))
6242  {
6244  {
6246  {
6247  TrainController->StopTTClockMessage(151, HeadCode + " failed to split - location too short at " + LocationName + ", reposition train if possible.");
6248  TrainController->LogActionError(6, HeadCode, "", FailLocTooShort, LocationName);
6250  }
6251  }
6252  Utilities->CallLogPop(1009); //these were inside above bracket & caused own detected fault on second access as didn't return, moved here at v2.19.0
6253  return; //this has been here for a very long time (at least from 2.0.0) but never reported! Either never reached this point or
6254  } //found & not reported. Same for RearTrainSplit
6255 
6256  if(TemporaryDelay)
6257  {
6259  Utilities->CallLogPop(2683);
6260  return;
6261  }
6262 
6263 //it is long enough for split
6264  AnsiString SplitTrainFixedDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->FixedDescription; //save these for new train before ActionVectorEntryPtr incremented
6265  AnsiString SplittingTrainDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6266  bool SplitTrainExplicitDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->ExplicitDescription;
6268 
6269  UnplotTrain(0);
6270  StartSpeed = 0;
6271  RearStartElement = RearTrainRearPos; //this is for the current train, not the new train which will attach to the front of this train
6272  for(int x = 0; x < 4; x++)
6273  {
6274  if(Track->TrackElementAt(1664, RearStartElement).Conn[x] == RearTrainFrontPos)
6275  {
6276  RearStartExitPos = x;
6277  }
6278  }
6279  StoppedAtLocation = true;
6280  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
6281  {
6282  StoppedWithoutPower = true;
6283  }
6284  PlotStartPosition(3);
6287 // ActionVectorEntryPtr++; moved lower down at v2.15.0 because of new section below
6289 
6290  //new at v2.15.0 for unequal split in mass & power
6291  int NewTrainMass;
6292  double NewTrainPowerAtRail;
6294  {
6295  int pos = ActionVectorEntryPtr->SplitDistribution.Pos('-');
6296  int MassPercent = ActionVectorEntryPtr->SplitDistribution.SubString(1, pos - 1).ToInt(); //validity checked during validation
6297  int PowerPercent = ActionVectorEntryPtr->SplitDistribution.SubString(pos + 1, ActionVectorEntryPtr->SplitDistribution.Length() - pos).ToInt();
6298  NewTrainMass = Mass * double(MassPercent)/100.0;
6299  Mass = Mass - NewTrainMass;
6300  NewTrainPowerAtRail = PowerAtRail * double(PowerPercent)/100.0;
6301  if(NewTrainPowerAtRail == 0)
6302  {
6303  NewTrainPowerAtRail = 0.08; //min value represents 0
6304  }
6305  PowerAtRail = PowerAtRail - NewTrainPowerAtRail;
6306  AValue = sqrt(2 * PowerAtRail / Mass);
6307  }
6308  else // same Mass, MaxBrakeRate & PowerAtRail as this train's halved values, and same MaxRunningSpeed as this train
6309  {
6310  Mass = Mass / 2;
6311  NewTrainMass = Mass;
6312  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).Mass = Mass;
6313  // MaxBrakeRate = MaxBrakeRate / 2; this was wrong - want brake rate to stay the same, brake force is halved but that not a train parameter
6314  // and when needed it's calculated from rate & mass - changed at v2.15.0
6315  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).MaxBrakeRate = MaxBrakeRate;
6316  PowerAtRail = PowerAtRail / 2;
6317  NewTrainPowerAtRail = PowerAtRail;
6318  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).PowerAtRail = PowerAtRail;
6319  AValue = sqrt(2 * PowerAtRail / Mass);
6320  // shouldn't change but include in case not set earlier
6321  }
6322 
6323  TActionEventType EventType = NoEvent;
6324  ActionVectorEntryPtr++; //moved here at v2.18.0 to give more chances to split in case points set wrongly initially, also when AddTrain TrainVector
6325  //may be repositioned so all references to this train may be invalid
6326 
6327  if(!TrainController->AddTrain(0, FrontTrainRearPos, FrontTrainFrontPos, OtherHeadCode, 0, NewTrainMass, MaxRunningSpeed, MaxBrakeRate, NewTrainPowerAtRail,
6328  "Timetable", LinkedTrainEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerMaxSpeed, false, EventType))
6329  // false for SignallerControl
6330  {
6331  Utilities->CallLogPop(1721); // EventType not used here
6332  // if fails either a throw will have been sent in AddTrain or start position failed prob because of
6333  // another train, in which case a message will have been sent to the perf log, also might well clear later
6334  // when other train moves away
6335  return;
6336  }
6337 
6338  TrainController->TrainVector.back().Description = SplitTrainFixedDescription; //added at v2.16.1, new train takes description from its TrainDataEntry
6339  if(!SplitTrainExplicitDescription) //new at v2.15.0 see above
6340  {
6341 // OldActionVectorEntryPtr->LinkedTrainEntryPtr->Description = OriginalDescription; dropped at v2.16.1
6342  TrainController->TrainVector.back().Description = SplittingTrainDescription; //else takes it from this train's description
6343  }
6344  TrainController->TrainVector.back().ActualArrivalTime = ActualArrivalTime; //added at v2.23.0, both trains take same arrival time
6345  TrainController->TrainVector.back().ArrivalMinDwellTime = ArrivalMinDwellTime; //added at v2.23.0, split train uses original MDT
6346  ArrivalMinDwellTime = ArrivalMinDwellTime + 30.0; //added at v2.23.0, original train adds 30 secs so split train departs first
6347  // Note data in 'this' now probably invalid as there has been a new addition to the TrainVector, so the train is likely to have a new address, hence make no more changes for the current train
6348  // see mods in UpdateTrain for v1.3.2
6349  TrainController->TrainAdded = true;
6350 
6351  TTrainOperatingData &TTOD = LinkedTrainEntryPtr->TrainOperatingDataVector.at(RepeatNumber); // this is for the newly created train
6352 
6353  TTOD.TrainID = TrainController->TrainVector.back().TrainID;
6354  TTOD.RunningEntry = Running;
6355  Utilities->CallLogPop(998);
6356 }
6357 
6358 // ---------------------------------------------------------------------------
6359 
6360 void TTrain::RearTrainSplit(int Caller) //Major rewrite at v2.18.0 using new ThisLocationLongEnoughForSplit
6361 {
6362 /*
6363  Split logic for is:- the final 4 train positions must overlap with the original train, & final 4 positions
6364  will maximise the number at the location. Note that this function isn't sophisticated enough to account for trains already at the
6365  location in determining the 4 positions, and will give a failure message if a train obstructs any of the 4 positions. In these
6366  circumstances the other train will need to be moved sufficiently away to release all 4 positions, then the train will split.
6367 */
6368  TrainController->LogEvent("" + AnsiString(Caller) + ",RearTrainSplit" + "," + HeadCode);
6369  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RearTrainSplit" + "," + HeadCode);
6370 
6371 /* restriction removed at v2.19.0
6372  if(PowerAtRail < 1)
6373  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can split when power restored
6374  {
6375  if(!ZeroPowerNoRearSplitMessage)
6376  {
6377  TrainController->StopTTClockMessage(176, HeadCode + ": A train without power can't split");
6378  }
6379  ZeroPowerNoRearSplitMessage = true;
6380  Utilities->CallLogPop(2685);
6381  return;
6382  }
6383 */
6384  AnsiString LocationName = Track->TrackElementAt(1676, LeadElement).ActiveTrackElementName;
6385 
6386  if(LocationName == "")
6387  {
6388  LocationName = Track->TrackElementAt(1677, MidElement).ActiveTrackElementName;
6389  }
6390  int RearTrainRearPos, RearTrainFrontPos;
6391  int FrontTrainRearPos, FrontTrainFrontPos, FrontTrainExitPos;
6393 
6394  if(LocationName == "")
6395  {
6396  throw Exception("Error - LocationName not set in RearTrainSplit");
6397  }
6398  // if message given call at ~5 sec intervals in case train repositioned
6399 
6400  bool TemporaryDelay = false;
6402  MidEntryPos, FrontTrainFrontPos, FrontTrainRearPos, RearTrainFrontPos, RearTrainRearPos, TemporaryDelay))
6403  {
6405  {
6407  {
6408  TrainController->StopTTClockMessage(177, HeadCode + " failed to split - location too short at " + LocationName + ", reposition train if possible.");
6409  TrainController->LogActionError(66, HeadCode, "", FailLocTooShort, LocationName);
6411  }
6412  }
6413  Utilities->CallLogPop(2686); //these were inside above bracket & caused own detected fault on second access as didn't return, moved here at v2.19.0
6414  return; //this has been here for a very long time (at least from 2.0.0) but never reported! Either never reached this point or
6415  } //found & not reported. Same for FrontTrainSplit
6416 
6417  if(TemporaryDelay)
6418  {
6420  Utilities->CallLogPop(2684);
6421  return;
6422  }
6423 
6424 //it is long enough for split
6425  AnsiString SplitTrainFixedDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->FixedDescription; //save these for new train before ActionVectorEntryPtr incremented
6426  AnsiString SplittingTrainDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6427  bool SplitTrainExplicitDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->ExplicitDescription;
6429 
6430  UnplotTrain(11);
6431  StartSpeed = 0;
6432  RearStartElement = FrontTrainRearPos; //this is for the current train, not the new train which will attach to the rear of this train
6433  for(int x = 0; x < 4; x++)
6434  {
6435  if(Track->TrackElementAt(1665, RearStartElement).Conn[x] == FrontTrainFrontPos)
6436  {
6437  RearStartExitPos = x;
6438  }
6439  }
6440  StoppedAtLocation = true;
6441  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
6442  {
6443  StoppedWithoutPower = true;
6444  }
6445  PlotStartPosition(12);
6448 // ActionVectorEntryPtr++; moved lower down at v2.15.0 because of new section below
6450 
6451  //new at v2.15.0 for unequal split in mass & power
6452  int NewTrainMass;
6453  double NewTrainPowerAtRail;
6455  {
6456  int pos = ActionVectorEntryPtr->SplitDistribution.Pos('-');
6457  int MassPercent = ActionVectorEntryPtr->SplitDistribution.SubString(1, pos - 1).ToInt(); //validity checked during validation
6458  int PowerPercent = ActionVectorEntryPtr->SplitDistribution.SubString(pos + 1, ActionVectorEntryPtr->SplitDistribution.Length() - pos).ToInt();
6459  NewTrainMass = Mass * double(MassPercent)/100.0;
6460  Mass = Mass - NewTrainMass;
6461  NewTrainPowerAtRail = PowerAtRail * double(PowerPercent)/100.0;
6462  if(NewTrainPowerAtRail == 0)
6463  {
6464  NewTrainPowerAtRail = 0.08; //min value represents 0
6465  }
6466  PowerAtRail = PowerAtRail - NewTrainPowerAtRail;
6467  AValue = sqrt(2 * PowerAtRail / Mass);
6468  }
6469  else // same Mass, MaxBrakeRate & PowerAtRail as this train's halved values, and same MaxRunningSpeed as this train
6470  {
6471  Mass = Mass / 2;
6472  NewTrainMass = Mass;
6473  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).Mass = Mass;
6474  // MaxBrakeRate = MaxBrakeRate / 2; this was wrong - want brake rate to stay the same, brake force is halved but that not a train parameter
6475  // and when needed it's calculated from rate & mass - changed at v2.15.0
6476  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).MaxBrakeRate = MaxBrakeRate;
6477  PowerAtRail = PowerAtRail / 2;
6478  NewTrainPowerAtRail = PowerAtRail;
6479  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).PowerAtRail = PowerAtRail;
6480  AValue = sqrt(2 * PowerAtRail / Mass);
6481  // shouldn't change but include in case not set earlier
6482  }
6483 
6484  TActionEventType EventType = NoEvent;
6485  ActionVectorEntryPtr++; //moved here at v2.18.0 to give more chances to split in case points set wrongly initially, also when AddTrain TrainVector
6486  //may be repositioned so all references to this train may be invalid
6487 
6488  if(!TrainController->AddTrain(4, RearTrainRearPos, RearTrainFrontPos, OtherHeadCode, 0, NewTrainMass, MaxRunningSpeed, MaxBrakeRate, NewTrainPowerAtRail,
6489  "Timetable", LinkedTrainEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerMaxSpeed, false, EventType))
6490  // false for SignallerControl
6491  {
6492  Utilities->CallLogPop(2687); // EventType not used here
6493  // if fails either a throw will have been sent in AddTrain or start position failed prob because of
6494  // another train, in which case a message will have been sent to the perf log, also might well clear later
6495  // when other train moves away
6496  return;
6497  }
6498 
6499  TrainController->TrainVector.back().Description = SplitTrainFixedDescription; //added at v2.16.1, new train takes description from its TrainDataEntry
6500  if(!SplitTrainExplicitDescription) //new at v2.15.0 see above
6501  {
6502 // OldActionVectorEntryPtr->LinkedTrainEntryPtr->Description = OriginalDescription; dropped at v2.16.1
6503  TrainController->TrainVector.back().Description = SplittingTrainDescription; //else takes it from this train's description
6504  }
6505  TrainController->TrainVector.back().ActualArrivalTime = ActualArrivalTime; //added at v2.23.0, both trains take same arrival time
6506  TrainController->TrainVector.back().ArrivalMinDwellTime = ArrivalMinDwellTime + 30.0; //added at v2.23.0, split train uses MDT + 30 so original train departs first
6507 
6508  // Note data in 'this' now probably invalid as there has been a new addition to the TrainVector, so the train is likely to have a new address, hence make no more changes for the current train
6509  // see mods in UpdateTrain for v1.3.2
6510  TrainController->TrainAdded = true;
6511 
6512  TTrainOperatingData &TTOD = LinkedTrainEntryPtr->TrainOperatingDataVector.at(RepeatNumber); // this is for the newly created train
6513 
6514  TTOD.TrainID = TrainController->TrainVector.back().TrainID;
6515  TTOD.RunningEntry = Running;
6516  Utilities->CallLogPop(2688);
6517 }
6518 
6519 // ---------------------------------------------------------------------------
6520 
6521 void TTrain::FinishJoin(int Caller) //this just sends appropriate messages, the main work including deleting this train is done by JoinedBy - see below
6522 {
6523  if(FinishJoinLogSent == false)
6524  {
6525  TrainController->LogEvent("" + AnsiString(Caller) + ",FinishJoin" + "," + HeadCode);
6526  FinishJoinLogSent = true; // so don't keep logging this event
6527  // don't need to reset it to false after the event as the train is deleted
6528  }
6529  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FinishJoin" + "," + HeadCode);
6530  if(TrainFailed)
6531  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can join when repaired) Can FinishJoin if zero power & not failed, as for empty stock
6532  {
6534  {
6535  TrainController->StopTTClockMessage(84, HeadCode + ": A failed train can't join another under timetable control");
6536  }
6538  Utilities->CallLogPop(2139);
6539  return;
6540  }
6541  if(TrainGone)
6542  // this means that the train has already joined the other & is awaiting deletion by TrainController
6543  // without this the 'waiting' message can be given since the other train's ActionVectorEntryPtr has moved
6544  // on from jbo & TrainToJoinIsAdjacent returns false
6545  {
6546  Utilities->CallLogPop(1035);
6547  return;
6548  }
6549  TTrain *TrainToJoin;
6551 
6552  if(!TrainToJoinIsAdjacent(0, TrainToJoin))
6553  {
6555  {
6556  // PerfLogForm->PerformanceLog(2, TrainController->TTClockTime.FormatString("hh:nn:ss") + ": " + HeadCode + " waiting to join " + JBOHeadCode + " at " + ActionVectorEntryPtr->LocationName);
6559  }
6560  Utilities->CallLogPop(1030);
6561  return; // keep this here in case need to add code before final return
6562  }
6563  // no need to clear error report flag here, cleared in jbo function
6564  // No need to set TimetableFinished, done in jbo function, and this train deleted in jbo function
6565  // here when other train is adjacent
6566  Utilities->CallLogPop(1031);
6567 }
6568 
6569 // ---------------------------------------------------------------------------
6570 
6571 void TTrain::JoinedBy(int Caller)
6572 {
6573  if(TrainController->OpTimeToActUpdateCounter == 0) //added at v2.13.2. Use OpTimeToActUpdateCounter for convenience so only issue the event log
6574  //once every second rather than many times. Can't use an event logged flag because there may
6575  //be several trains that are to be joined by others
6576  {
6577  TrainController->LogEvent("" + AnsiString(Caller) + "," + HeadCode + ",Waiting to be joined");
6578  }
6579  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",JoinedBy" + "," + HeadCode);
6580 
6581 /* restriction removed at v2.19.0
6582  if(PowerAtRail < 1)
6583  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can join when power restored)
6584  {
6585  if(!ZeroPowerNoJoinedByMessage)
6586  {
6587  TrainController->StopTTClockMessage(85, HeadCode + ": A train without power can't be joined by another under timetable control");
6588  }
6589  ZeroPowerNoJoinedByMessage = true;
6590  Utilities->CallLogPop(2140);
6591  return;
6592  }
6593 */
6594  TTrain *TrainToBeJoinedBy;
6596 
6597  if(!TrainToBeJoinedByIsAdjacent(0, TrainToBeJoinedBy))
6598  {
6600  {
6601  // PerfLogForm->PerformanceLog(3, TrainController->TTClockTime.FormatString("hh:nn:ss") + ": " + HeadCode + " waiting to be joined by " + FJOHeadCode + " at " + ActionVectorEntryPtr->LocationName);
6604  }
6605  LastActionDelayFlag = true; //not used at v2.23.0 but keep as included in session file
6606  // need to update LastActionTime if this train first to arrive as need 30s after
6607  // both adjacent before the join
6608  Utilities->CallLogPop(1032);
6609  return;
6610  }
6611  // here when other train is adjacent
6612 // if(LastActionDelayFlag) //drop this as if Fjo train arrives first it won't be set
6613  {
6614  if(TrainToBeJoinedBy->LastActionTime > LastActionTime) //LastActionTime for this train already set when arrived
6615  {
6616  LastActionTime = TrainToBeJoinedBy->LastActionTime;
6617  }
6618  if((double(TrainToBeJoinedBy->ActualArrivalTime) + (TrainToBeJoinedBy->ArrivalMinDwellTime / 86400)) > (double(ActualArrivalTime) + (ArrivalMinDwellTime / 86400)))
6619  {
6620  ActualArrivalTime = TrainToBeJoinedBy->ActualArrivalTime;
6621  ArrivalMinDwellTime = TrainToBeJoinedBy->ArrivalMinDwellTime; //these set so that both trains get at least their required min dwell times
6622  }
6623  // need to update this as need 30s after both adjacent before the join
6624  if(TrainController->TTClockTime < (LastActionTime + TDateTime(30.0 / 86400))) //don't move on until 30s after last train to arrive + 30 secs
6625  {
6626  LastActionDelayFlag = false;
6627  Utilities->CallLogPop(1033);
6628  return;
6629  }
6630  }
6631  // here when other train is adjacent & 30 secs elapsed since both adjacent
6632 
6633  // set new values for mass etc
6634  if(MaxRunningSpeed > TrainToBeJoinedBy->MaxRunningSpeed)
6635  {
6636  MaxRunningSpeed = TrainToBeJoinedBy->MaxRunningSpeed;
6637  }
6638  double OtherBrakeForce = TrainToBeJoinedBy->MaxBrakeRate * TrainToBeJoinedBy->Mass;
6639  double OwnBrakeForce = MaxBrakeRate * Mass;
6640  double CombinedBrakeRate = (OtherBrakeForce + OwnBrakeForce) / (TrainToBeJoinedBy->Mass + Mass);
6641 
6642  Mass += TrainToBeJoinedBy->Mass;
6643  MaxBrakeRate = CombinedBrakeRate;
6644  PowerAtRail += TrainToBeJoinedBy->PowerAtRail;
6645  AValue = sqrt(2 * PowerAtRail / Mass);
6646 
6648  TrainToBeJoinedBy->TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = NoEvent;
6649  TrainToBeJoinedBy->TimetableFinished = true;
6650  TrainToBeJoinedBy->TrainGone = true;
6651  TrainController->LogEvent("" + AnsiString(Caller) + "," + HeadCode + ",Joined By," + FJOHeadCode); //added at v2.13.2 to provide more information
6652  // this will cause other train to be deleted
6653  TrainToBeJoinedBy->JoinedOtherTrainFlag = true;
6657  Utilities->CallLogPop(1034);
6658 }
6659 
6660 // ---------------------------------------------------------------------------
6661 
6662 void TTrain::ChangeTrainDirection(int Caller, bool NoLogFlag)
6663 {
6664  TrainController->LogEvent("" + AnsiString(Caller) + ",ChangeTrainDirection" + "," + HeadCode);
6665  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ChangeTrainDirection" + "," + HeadCode);
6666 
6667  /* restriction removed at v2.19.0
6668  if(PowerAtRail < 1)
6669  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can change direction when power restored)
6670  {
6671  if(!ZeroPowerNoCDTMessage)
6672  {
6673  TrainController->StopTTClockMessage(86, HeadCode + ": A train without power can't change direction under timetable control");
6674  }
6675  ZeroPowerNoCDTMessage = true;
6676  Utilities->CallLogPop(2141);
6677  return;
6678  }
6679 */
6680  TColor TempColour = BackgroundColour;
6681 
6682  UnplotTrain(2);
6685  StartSpeed = 0;
6686  StoppedAtLocation = true;
6687  PlotStartPosition(1);
6688  PlotTrainWithNewBackgroundColour(27, TempColour, Display);
6689  // plot same as was - should always be pale green
6690  if(!NoLogFlag)
6691  {
6694  }
6696 
6697  //now erase a stub route if there is one, added at v2.5.1
6698  //first element of route is now immediately behind the train (i.e. next to MidElement)
6699  if(MidEntryPos >= 0)
6700  {
6701  TTrackElement MidTrackElement = Track->TrackElementAt(996, MidElement);
6702  int FirstRouteElementVecPos = MidTrackElement.Conn[MidEntryPos];
6703  int FirstRouteLinkPos = MidTrackElement.ConnLinkPos[MidEntryPos];
6704  int RouteNumber = -1;
6705  TAllRoutes::TRouteType RouteType = AllRoutes->GetRouteTypeAndNumber(34, FirstRouteElementVecPos, FirstRouteLinkPos, RouteNumber);
6706  if(RouteType == TAllRoutes::NotAutoSigsRoute)
6707  {
6708  TOneRoute &OR = AllRoutes->GetModifiableRouteAt(28, RouteNumber);
6709  int CorrectRouteID = OR.RouteID; //added at v2.13.0 as when last element removed & route removed from vector OR becomes the next route after the erased one and
6710  //elements can continue to be removed from that route
6711  TTrackElement TE = Track->TrackElementAt(997, FirstRouteElementVecPos);
6712 // if((TE.TrackType != SignalPost) && (TE.TrackType != Continuation)) //all autosigs routes have signalpost or continuation at 0 so they are automatically excluded
6713  { //above condition removed v2.17.0 so non-facing signal or continuation doesn't stop route being removed
6714  //if it is a facing signal then it will be detected below and not removed
6715  bool FirstPass = true; //added at v2.8.0
6716  while((OR.PrefDirSize() > 0) && (OR.RouteID == CorrectRouteID)) //remove the route up to but not including the next facing signal, in case a pref dir route extends to another signal
6717  { // && (OR.RouteID == RouteID) added at v2.13.0 to prevent another route having elements removed
6718  TPrefDirElement PDE = OR.GetFixedPrefDirElementAt(247, 0); //these will change at each element removal because OR is a reference to the real route
6719  int TVPos2 = PDE.GetTrackVectorPosition();
6720  if(FirstPass && (TVPos2 != FirstRouteElementVecPos)) //route is not directed away from cdt train, could be a call-on for another train (added at v2.8.0)
6721  {
6722  break;
6723  }
6724  TTrackElement TE2 = Track->TrackElementAt(998, TVPos2);
6726  {
6727  AllRoutes->RemoveRouteElement(22, TE2.HLoc, TE2.VLoc, PDE.GetELink());
6728  }
6729  else
6730  {
6731  break;
6732  }
6733  FirstPass = false;
6734  }
6735  AllRoutes->RebuildRailwayFlag = true;
6736  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot without stub route
6737  }
6738  }
6739  }
6740  Utilities->CallLogPop(1012);
6741 }
6742 
6743 // ---------------------------------------------------------------------------
6744 
6745 void TTrain::NewTrainService(int Caller, bool NoLogFlag) //, bool NoLogFlag added at v2.12.0 for new service tt skips
6746 // change to new train, give new service message
6747 //same RepeatNumber used for the new service
6748 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
6749  TrainController->LogEvent("" + AnsiString(Caller) + ",NewTrainService" + "," + HeadCode);
6750  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NewTrainService" + "," + HeadCode);
6751 
6752 /* restriction removed at v2.19.0
6753  if(PowerAtRail < 1)
6754  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can form new service when power restored)
6755  {
6756  if(!ZeroPowerNoNewServiceMessage)
6757  {
6758  TrainController->StopTTClockMessage(87, HeadCode + ": A train without power can't form a new service");
6759  }
6760  ZeroPowerNoNewServiceMessage = true;
6761  Utilities->CallLogPop(2142);
6762  return;
6763  }
6764 */
6766 
6767  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6768 
6769  if(!NoLogFlag)
6770  {
6772  }
6773  UnplotTrain(3);
6776  StartSpeed = 0;
6781  HeadCode = NewHeadCode;
6783  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6784  {
6785  Description = OriginalDescription; //changed at v2.16.1 to train description
6786  }
6787  StoppedAtLocation = true;
6788  PlotStartPosition(5);
6790  // pale green
6793  TerminatedMessageSent = false;
6794 //Utilities->Pause(1000); //diagnostics
6795  Utilities->CallLogPop(1022);
6796 }
6797 
6798 // ---------------------------------------------------------------------------
6799 
6800 void TTrain::RemainHere(int Caller) //added warnings & reminders at v2.19.0 (not sent from LogAction as not called when train terminates)
6801 {
6802  Utilities->CallLog.push_back(Utilities->TimeStamp() + AnsiString(Caller) + ",RemainHere" + "," + HeadCode);
6803  if(RemainHereLogNotSent) // to prevent repeated logs
6804  {
6805  TrainController->LogEvent(Utilities->TimeStamp() + AnsiString(Caller) + ",RemainHere" + "," + HeadCode);
6806  RemainHereLogNotSent = false;
6807  }
6809  {
6810  TDateTime ActualTime = TrainController->TTClockTime;
6811  AnsiString BaseLog = "", Location = ActionVectorEntryPtr->LocationName;
6812  AnsiString PerfLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + " terminated at " + Location;
6814  {
6815  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " WARNING: " + HeadCode + " terminated at " + Location;
6816  Display->WarningLog(25, PerfLog);
6817  PerfLogForm->PerformanceLog(65, BaseLog);
6818  }
6819  else if(ActionVectorEntryPtr->Reminder > 0)
6820  {
6821  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + " terminated at " + Location;
6822  Display->WarningLog(26, PerfLog);
6823  PerfLogForm->PerformanceLog(66, BaseLog);
6824  }
6825  else
6826  {
6827  PerfLogForm->PerformanceLog(67, PerfLog);
6828  }
6830  TerminatedMessageSent = true;
6831  }
6832  TimetableFinished = true;
6833  Utilities->CallLogPop(1023);
6834 }
6835 
6836 // ---------------------------------------------------------------------------
6837 
6838 void TTrain::SendMissedActionLogs(int Caller, int IncNum, TActionVectorEntry *Ptr)
6839 /*
6840  Enter with pointer at next expected action, and IncNum the number by which have to increase the pointer
6841  to reach the action that is valid for the train's current position. i.e. IncNum error messages to be sent
6842  except where an action is a departure, starting at the current value for the pointer
6843  If IncNum is -1, then send messages for all remaining actions, including Fer if present
6844  If IncNum is -2, then send messages for all remaining actions, except Fer if present
6845 */{
6846  if((Ptr->Command == "Snt") && Ptr->SignallerControl)
6847  {
6848  return; // if remove train that starts under signaller control no messages needed
6849 
6850  }
6851  TrainController->LogEvent("" + AnsiString(Caller) + ",SendMissedActionLogs," + AnsiString(IncNum) + "," + HeadCode);
6852  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SendMissedActionLogs," + AnsiString(IncNum) + "," + HeadCode);
6853  if(IncNum > 0)
6854  {
6855  for(int x = 0; x < IncNum; x++)
6856  {
6857  if(x > 0)
6858  {
6859  Ptr++;
6860  }
6861  // arrival - no need to test for termination as this section only covers missed actions up to the
6862  // arrival point - may terminate later but that not missed
6863  if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime > TDateTime(-1)))
6864  {
6866  }
6867  // arrival & departure
6868  if(Ptr->FormatType == TimeTimeLoc)
6869  {
6871  }
6872  // departure
6873  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
6874  {
6875  continue; // skip TimeLoc departures, message given for arrivals
6876  }
6877  // pass
6878  else if(Ptr->FormatType == PassTime)
6879  {
6881  }
6882  // split
6883  else if((Ptr->Command == "fsp") || (Ptr->Command == "rsp"))
6884  {
6886  }
6887  // jbo
6888  else if(Ptr->Command == "jbo")
6889  {
6891  }
6892  // dsc
6893  else if(Ptr->Command == "dsc") //new at v2.15.0
6894  {
6896  }
6897  //cms
6898  else if(Ptr->Command == "cms") //new at v2.21.0
6899  {
6901  }
6902  // Errors - have reached a station stop point (before a cdt) during Train->Update() so intervening actions can't
6903  // be starts, finishes or cdt
6904  else if((Ptr->Command == "Fns") || (Ptr->Command == "Frh") || (Ptr->Command == "Fer") || (Ptr->Command == "Fjo") || (Ptr->Command == "Snt") ||
6905  (Ptr->Command == "Sfs") || (Ptr->Command == "Snt-sh") || (Ptr->Command == "Sns") || (Ptr->Command == "Sns-sh") || (Ptr->Command == "Sns-fsh") ||
6906  (Ptr->Command == "cdt") || (Ptr->Command == "Frh-sh") || (Ptr->Command == "Fns-sh") || (Ptr->Command == "F-nshs") ||
6907  (Ptr->FormatType == Repeat))
6908  {
6909  throw Exception("Error - illegal command in SendMissedActionLogs for IncNum = " + AnsiString(IncNum) + ", and command = " + Ptr->Command);
6910  }
6911  }
6912  }
6913  else
6914  {
6915  bool IncludeFER = false;
6916  if(IncNum == -1)
6917  {
6918  IncludeFER = true;
6919  }
6920  while(true) // finish commands & repeats break out of loop
6921  {
6922  // Fer & excluded - send normal exit log to give minutes late or early - no, have already sent an unexpected exit message
6923  if(!IncludeFER && (Ptr->Command == "Fer"))
6924  {
6925  break;
6926  }
6927  // Fer & included
6928  else if(IncludeFER && (Ptr->Command == "Fer"))
6929  {
6931  break;
6932  }
6933  // Repeat
6934  else if(Ptr->FormatType == Repeat)
6935  {
6936  break;
6937  }
6938  // Fjo
6939  else if(Ptr->Command == "Fjo")
6940  {
6942  break;
6943  }
6944  // Frh
6945  else if(Ptr->Command == "Frh")
6946  {
6948  {
6950  TerminatedMessageSent = true;
6951  }
6952  break;
6953  }
6954  // Frh-sh
6955  else if(Ptr->Command == "Frh-sh")
6956  {
6958  {
6960  TerminatedMessageSent = true;
6961  }
6962  break;
6963  }
6964  // Fns, F-nshs, Fns-sh
6965  else if((Ptr->Command == "Fns") || (Ptr->Command == "F-nshs") || (Ptr->Command == "Fns-sh"))
6966  {
6968  break;
6969  }
6970  // end of breakout actions
6971  // arrival
6972  if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime > TDateTime(-1)))
6973  {
6974  if(IsTrainTerminating(1))
6975  {
6977  TerminatedMessageSent = true;
6978  }
6979  else
6980  {
6982  }
6983  }
6984  // arrival & departure
6985  else if(Ptr->FormatType == TimeTimeLoc)
6986  {
6988  }
6989  // departure
6990  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
6991  {
6992  Ptr++;
6993  continue; // skip TimeLoc departures, message given for arrivals
6994  }
6995  // pass
6996  else if(Ptr->FormatType == PassTime)
6997  {
6999  }
7000  // split
7001  else if((Ptr->Command == "fsp") || (Ptr->Command == "rsp"))
7002  {
7004  }
7005  // jbo
7006  else if(Ptr->Command == "jbo")
7007  {
7009  }
7010  // dsc
7011  else if(Ptr->Command == "dsc") //new at v2.15.0
7012  {
7013 // TrainController->LogActionError(65, HeadCode, "", FailMissedDSC, Ptr->LocationName); don't count as a missed event
7014  }
7015  // cms
7016  else if(Ptr->Command == "cms") //new at v2.21.0
7017  {
7018 // TrainController->LogActionError(, HeadCode, "", FailMissedCMS, Ptr->LocationName); don't count as a missed event
7019  }
7020  // cdt
7021  else if(Ptr->Command == "cdt")
7022  {
7023 // TrainController->LogActionError(25, HeadCode, "", FailMissedChangeDirection, Ptr->LocationName); //commented out at v2.12.0 as cdts not counted as missed events
7024  }
7025  // Errors
7026  else if((Ptr->Command == "Snt-sh") || (Ptr->Command == "Sfs") || (Ptr->Command == "Sns") || (Ptr->Command == "Sns-sh") ||
7027  (Ptr->Command == "Sns-fsh") || ((Ptr->Command == "Snt") && !Ptr->SignallerControl))
7028  {
7029  throw Exception("Error - illegal command in SendMissedActionLogs for IncNum = " + AnsiString(IncNum) + ", and command = " + Ptr->Command);
7030  }
7031  Ptr++;
7032  }
7033  TimetableFinished = true;
7034  }
7035  Utilities->CallLogPop(1021);
7036 }
7037 
7038 // ---------------------------------------------------------------------------
7039 
7040 bool TTrain::TrainToJoinIsAdjacent(int Caller, TTrain* &TrainToJoin)
7041 // ensure same repeatnumber
7042 {
7043  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainToJoinIsAdjacent" + "," + HeadCode);
7045 
7046  if(TrainToJoinTDEntry->TrainOperatingDataVector.at(RepeatNumber).RunningEntry != Running)
7047  {
7048  Utilities->CallLogPop(1024);
7049  return(false);
7050  }
7051  TrainToJoin = &(TrainController->TrainVectorAtIdent(33, TrainToJoinTDEntry->TrainOperatingDataVector.at(RepeatNumber).TrainID));
7052  if(TrainToJoin->StoppedAtLocation && (TrainToJoin->TrainMode == Timetable) && (TrainToJoin->ActionVectorEntryPtr->Command == "jbo"))
7053  {
7054  if((Track->TrackElementAt(610, LeadElement).Conn[LeadExitPos] == TrainToJoin->LeadElement) || (Track->TrackElementAt(611,
7055  LeadElement).Conn[LeadExitPos] == TrainToJoin->MidElement) || (Track->TrackElementAt(612, MidElement).Conn[MidEntryPos] == TrainToJoin->LeadElement)
7056  || (Track->TrackElementAt(613, MidElement).Conn[MidEntryPos] == TrainToJoin->MidElement))
7057  {
7058  Utilities->CallLogPop(1025);
7059  return(true);
7060  }
7061  }
7062  Utilities->CallLogPop(1026);
7063  return(false);
7064 }
7065 
7066 // ---------------------------------------------------------------------------
7067 
7068 bool TTrain::TrainToBeJoinedByIsAdjacent(int Caller, TTrain* &TrainToBeJoinedBy)
7069 // ensure same repeatnumber
7070 {
7071  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainToBeJoinedByIsAdjacent" + "," + HeadCode);
7072  TTrainDataEntry *TrainToBeJoinedByTDEntry = ActionVectorEntryPtr->LinkedTrainEntryPtr;
7073 
7074  if(TrainToBeJoinedByTDEntry->TrainOperatingDataVector.at(RepeatNumber).RunningEntry != Running)
7075  {
7076  Utilities->CallLogPop(1027);
7077  return(false);
7078  }
7079  TrainToBeJoinedBy = &(TrainController->TrainVectorAtIdent(15, TrainToBeJoinedByTDEntry->TrainOperatingDataVector.at(RepeatNumber).TrainID));
7080  if(TrainToBeJoinedBy->StoppedAtLocation && (TrainToBeJoinedBy->TrainMode == Timetable) && (TrainToBeJoinedBy->ActionVectorEntryPtr->Command == "Fjo"))
7081  {
7082  if((Track->TrackElementAt(614, LeadElement).Conn[LeadExitPos] == TrainToBeJoinedBy->LeadElement) || (Track->TrackElementAt(615,
7083  LeadElement).Conn[LeadExitPos] == TrainToBeJoinedBy->MidElement) || (Track->TrackElementAt(616,
7084  MidElement).Conn[MidEntryPos] == TrainToBeJoinedBy->LeadElement) || (Track->TrackElementAt(617,
7085  MidElement).Conn[MidEntryPos] == TrainToBeJoinedBy->MidElement))
7086  {
7087  Utilities->CallLogPop(1028);
7088  return(true);
7089  }
7090  }
7091  Utilities->CallLogPop(1029);
7092  return(false);
7093 }
7094 
7095 // ---------------------------------------------------------------------------
7096 
7097 void TTrain::NewShuttleFromNonRepeatService(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips)
7098 { //same RepeatNumber (i.e. 0) used for the new shuttle
7099 //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
7100  TrainController->LogEvent("" + AnsiString(Caller) + ",NewShuttleFromNonRepeatService" + "," + HeadCode);
7101  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NewShuttleFromNonRepeatService" + "," + HeadCode);
7102 
7103 /* restriction removed at v2.19.0
7104  if(PowerAtRail < 1)
7105  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
7106  {
7107  if(!ZeroPowerNoNewShuttleFromNonRepeatMessage)
7108  {
7109  TrainController->StopTTClockMessage(88, HeadCode + ": A train without power can't form a new service");
7110  }
7111  ZeroPowerNoNewShuttleFromNonRepeatMessage = true;
7112  Utilities->CallLogPop(2143);
7113  return;
7114  }
7115 */
7116  AnsiString NewHeadCode = ActionVectorEntryPtr->NonRepeatingShuttleLinkHeadCode;
7117  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7118 
7119  if(!NoLogFlag)
7120  {
7122  }
7123  UnplotTrain(4);
7126  StartSpeed = 0;
7131  HeadCode = NewHeadCode;
7133  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7134  {
7135  Description = OriginalDescription; //changed at v2.16.1 to train description
7136  }
7137  IncrementalMinutes = TrainDataEntryPtr->ActionVector.back().RearStartOrRepeatMins;
7138  IncrementalDigits = TrainDataEntryPtr->ActionVector.back().FrontStartOrRepeatDigits;
7139  StoppedAtLocation = true;
7140  PlotStartPosition(6);
7142  // pale green
7145  TerminatedMessageSent = false;
7146  Utilities->CallLogPop(1078);
7147 }
7148 
7149 // ---------------------------------------------------------------------------
7150 
7151 void TTrain::RepeatShuttleOrRemainHere(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips)
7152 // need to check whether all repeats finished or not
7153 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
7154  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RepeatShuttleOrRemainHere" + "," + HeadCode);
7155  if(RemainHereLogNotSent) // to prevent repeated logs
7156  {
7157  TrainController->LogEvent("" + AnsiString(Caller) + ",RepeatShuttleOrRemainHere" + "," + HeadCode);
7158  RemainHereLogNotSent = false;
7159  }
7161  // finished all repeats
7162  {
7164  { //no need to suppress this LogAction because BecomeNewService won't be available in this case
7167  TerminatedMessageSent = true;
7168  // no need to clear message as no more actions
7169  }
7170  TimetableFinished = true;
7171  Utilities->CallLogPop(1080);
7172  return;
7173  }
7174 
7175 /* restriction removed at v2.19.0
7176  if(PowerAtRail < 1)
7177  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
7178  {
7179  if(!ZeroPowerNoRepeatShuttleMessage)
7180  {
7181  TrainController->StopTTClockMessage(89, HeadCode + ": A train without power can't form a new service");
7182  }
7183  ZeroPowerNoRepeatShuttleMessage = true;
7184  Utilities->CallLogPop(2144);
7185  return;
7186  }
7187 */
7188  int TempRepeatNumber = RepeatNumber + 1;
7189  // need the next repeat value in order to obtain a correct NewHeadCode, but don't increase it
7190  // until after LogAction or the wrong time will be used
7191  AnsiString NewHeadCode = TrainController->GetRepeatHeadCode(6, ActionVectorEntryPtr->OtherHeadCode, TempRepeatNumber, IncrementalDigits);
7192  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7193 
7194  if(!NoLogFlag)
7195  {
7197  }
7198  RepeatNumber++;
7199  UnplotTrain(5);
7202  StartSpeed = 0;
7207  HeadCode = NewHeadCode;
7209  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7210  {
7211  Description = OriginalDescription; //changed at v2.16.1 to train description
7212  }
7213  StoppedAtLocation = true;
7214  PlotStartPosition(7);
7216  // pale green
7219  TerminatedMessageSent = false;
7220  Utilities->CallLogPop(1079);
7221 }
7222 
7223 // ---------------------------------------------------------------------------
7224 
7225 void TTrain::RepeatShuttleOrNewNonRepeatService(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips
7226 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
7227  TrainController->LogEvent("" + AnsiString(Caller) + ",RepeatShuttleOrNewNonRepeatService" + "," + HeadCode);
7228  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RepeatShuttleOrNewNonRepeatService" + "," + HeadCode);
7229 
7230 /* restriction removed at v2.19.0
7231  if(PowerAtRail < 1)
7232  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
7233  {
7234  if(!ZeroPowerNoRepeatShuttleOrNewServiceMessage)
7235  {
7236  TrainController->StopTTClockMessage(90, HeadCode + ": A train without power can't form a new service");
7237  }
7238  ZeroPowerNoRepeatShuttleOrNewServiceMessage = true;
7239  Utilities->CallLogPop(2145);
7240  return;
7241  }
7242 */
7244  // finished all repeats
7245  {
7246  AnsiString NewHeadCode = ActionVectorEntryPtr->NonRepeatingShuttleLinkHeadCode;
7247  if(!NoLogFlag)
7248  {
7250  }
7251  RepeatNumber = 0;
7252  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7253  UnplotTrain(6);
7256  StartSpeed = 0;
7258  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).TrainID = TrainID; // but note that RepeatNumber = 0
7259  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).RunningEntry = Running; // but note that RepeatNumber = 0
7261  HeadCode = NewHeadCode;
7263  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7264  {
7265  Description = OriginalDescription; //changed at v2.16.1 to train description
7266  }
7267  StoppedAtLocation = true;
7268  PlotStartPosition(9);
7272  TerminatedMessageSent = false;
7273  Utilities->CallLogPop(1081);
7274  return;
7275  }
7276  int TempRepeatNumber = RepeatNumber + 1;
7277  // need the next repeat value in order to obtain a correct NewHeadCode, but don't increase it
7278  // until after LogAction or the wrong time will be used
7279  AnsiString NewHeadCode = TrainController->GetRepeatHeadCode(7, ActionVectorEntryPtr->OtherHeadCode, TempRepeatNumber, IncrementalDigits);
7280  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7281 
7282  if(!NoLogFlag)
7283  {
7285  }
7286  RepeatNumber++;
7287  UnplotTrain(7);
7290  StartSpeed = 0;
7295  HeadCode = NewHeadCode;
7297  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7298  {
7299  Description = OriginalDescription; //changed at v2.16.1 to train description
7300  }
7301  StoppedAtLocation = true;
7302  PlotStartPosition(8);
7304  // pale green
7307  TerminatedMessageSent = false;
7308  Utilities->CallLogPop(1082);
7309 }
7310 
7311 // ---------------------------------------------------------------------------
7312 
7314 {
7315  // Search ActionVector from the position after the entry value for Ptr to the end, and return true if find a Finish
7316  // entry before Fer or TimeLoc. No point checking for TimeTimeLoc since at a stop location now so a later TimeTimeLoc
7317  // must be preceded by a TimeLoc departure
7318  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainTerminating" + "," + HeadCode);
7319  for(unsigned int x = 1; x < TrainDataEntryPtr->ActionVector.size(); x++)
7320  {
7322  {
7323  if(((ActionVectorEntryPtr + x)->Command == "Fer") || ((ActionVectorEntryPtr + x)->FormatType == TimeLoc))
7324  {
7325  Utilities->CallLogPop(1083);
7326  return(false);
7327  }
7328  else if((ActionVectorEntryPtr + x)->SequenceType == FinishSequence)
7329  {
7330  Utilities->CallLogPop(1084);
7331  return(true);
7332  }
7333  }
7334  }
7335  Utilities->CallLogPop(1085);
7336  return(false);
7337 }
7338 
7339 // ---------------------------------------------------------------------------
7340 
7341 bool TTrain::AbleToMove(int Caller)
7342 {
7343  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AbleToMove" + "," + HeadCode);
7344  bool Able = true;
7345 
7346  if(Crashed || Derailed || StoppedAtBuffers || StoppedAtSignal || StoppedWithoutPower) // StoppedWithoutPower added at v2.4.0
7347  {
7348  // StoppedForTrainInFront removed as tested below
7349  Utilities->CallLogPop(2146); // added v2.4.0
7350  return(false); // added v2.4.0
7351  }
7352  if(LeadElement > -1)
7353  {
7354  if(Track->TrackElementAt(801, LeadElement).TrackType == Buffers) //moved up here from 'else' below at v2.12.0
7355  {
7356  StoppedForTrainInFront = false;
7357  TrainInFront = false;
7358  // don't set StoppedAtBuffers as (presumably) StoppedAtLocation & leave it at that
7359  Utilities->CallLogPop(2456);
7360  return(false);
7361  }
7362  int FrontPos = Track->TrackElementAt(678, LeadElement).Conn[LeadExitPos];
7363  int FrontEntryPos = Track->TrackElementAt(679, LeadElement).ConnLinkPos[LeadExitPos];
7364  if((FrontPos > -1) && (TrainMode == Signaller) && TrainInFront) //check if train in front still there
7365  {
7366  TTrackElement TrackElement = Track->TrackElementAt(680, FrontPos);
7367  if((TrackElement.TrackType != Bridge) && (TrackElement.TrainIDOnElement == -1))
7368  {
7369  Able = true;
7370  TrainInFront = false;
7371  }
7372  else if((TrackElement.TrackType == Bridge) && (FrontEntryPos < 2) && (TrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 == -1))
7373  {
7374  Able = true;
7375  TrainInFront = false;
7376  }
7377  else if((TrackElement.TrackType == Bridge) && (FrontEntryPos > 1) && (TrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 == -1))
7378  {
7379  Able = true;
7380  TrainInFront = false;
7381  }
7382  else
7383  {
7384  Able = false; //added at v2.12.0 as train still in front so don't want signaller popup options to move available
7385  }
7386  }
7387  }
7388  else // leaving at a continuation so keep going
7389  {
7390  Able = true;
7391  StoppedForTrainInFront = false;
7392  TrainInFront = false;
7393  }
7394  Utilities->CallLogPop(1454);
7395  return(Able);
7396 }
7397 
7398 // ---------------------------------------------------------------------------
7399 
7401 {
7402  // first check if a train immediately in front (may have moved there since this train stopped so StoppedForTrainInFront
7403  // won't be set; if there is a train then set StoppedForTrainInFront
7404  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AbleToMoveButForSignalOrTrainInFront" + "," + HeadCode);
7405  // addition below for v1.3.2 after Carwyn Thomas fault reported 24/05/15 - need to check if exiting at continuation (LeadElement == -1) as if so fails at VecPos = .....
7406  if(LeadElement == -1) // exiting at continuation
7407  {
7408  Utilities->CallLogPop(2045);
7409  return(false);
7410  }
7411  // end of addition
7412  int VecPos = Track->TrackElementAt(654, LeadElement).Conn[LeadExitPos];
7413  int NextEntryPos = Track->TrackElementAt(655, LeadElement).ConnLinkPos[LeadExitPos];
7414 
7415  if(Track->OtherTrainOnTrack(5, VecPos, NextEntryPos, TrainID))
7416  {
7417  TrainInFront = true;
7418  Utilities->CallLogPop(1455);
7419  return(false);
7420  }
7421  else
7422  {
7423  Utilities->CallLogPop(1456);
7425  // StoppedWithoutPower added v2.4.0
7426  }
7427 }
7428 
7429 // ---------------------------------------------------------------------------
7430 
7432 {
7433  // unplots & replots train, which checks for facing signal and sets StoppedAtSignal if req'd
7434  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SignallerChangeTrainDirection" + "," + HeadCode);
7435  TColor TempColour = BackgroundColour;
7436 
7437  UnplotTrain(8);
7440  StartSpeed = 0;
7441  PlotStartPosition(2);
7442  PlotTrainWithNewBackgroundColour(26, TempColour, Display);
7443 
7444  //now erase a stub route if there is one, added at v2.5.1
7445  //first element of route is now immediately behind the train (i.e. next to MidElement)
7446  if(MidEntryPos >= 0)
7447  {
7448  TTrackElement MidTrackElement = Track->TrackElementAt(1000, MidElement);
7449  int FirstRouteElementVecPos = MidTrackElement.Conn[MidEntryPos];
7450  int FirstRouteLinkPos = MidTrackElement.ConnLinkPos[MidEntryPos];
7451  int RouteNumber = -1;
7452  TAllRoutes::TRouteType RouteType = AllRoutes->GetRouteTypeAndNumber(35, FirstRouteElementVecPos, FirstRouteLinkPos, RouteNumber);
7453  if(RouteType == TAllRoutes::NotAutoSigsRoute)
7454  {
7455  TOneRoute &OR = AllRoutes->GetModifiableRouteAt(29, RouteNumber);
7456  int CorrectRouteID = OR.RouteID; //added at v2.13.0 as when last element removed & route removed from vector OR becomes the next route after the erased one and
7457  //elements can continue to be removed from that route
7458  TTrackElement TE = Track->TrackElementAt(1001, FirstRouteElementVecPos);
7459 // if((TE.TrackType != SignalPost) && (TE.TrackType != Continuation)) //all autosigs routes have signalpost or continuation at 0 so they are automatically excluded
7460  { //above condition removed at v2.17.0 so non-facing signal or continuation doesn't stop route being removed
7461  //if it is a facing signal then it will be detected below and not removed
7462  bool FirstPass = true; //added at v2.8.0
7463  while((OR.PrefDirSize() > 0) && (OR.RouteID == CorrectRouteID)) //remove the route up to but not including the next facing signal, in case a pref dir route extends to another signal
7464  { // && (OR.RouteID == RouteID) added at v2.13.0 to prevent another route having elements removed
7465  TPrefDirElement PDE = OR.GetFixedPrefDirElementAt(248, 0); //these will change at each element removal because OR is a reference to the real route
7466  int TVPos2 = PDE.GetTrackVectorPosition();
7467  if(FirstPass && (TVPos2 != FirstRouteElementVecPos)) //route is not directed away from cdt train, could be a call-on for another train (added at v2.8.0)
7468  {
7469  break;
7470  }
7471  TTrackElement TE2 = Track->TrackElementAt(1002, TVPos2);
7473  {
7474  AllRoutes->RemoveRouteElement(23, TE2.HLoc, TE2.VLoc, PDE.GetELink());
7475  }
7476  else
7477  {
7478  break;
7479  }
7480  FirstPass = false;
7481  }
7482  AllRoutes->RebuildRailwayFlag = true;
7483  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot without stub route
7484  }
7485  }
7486  }
7487  Utilities->CallLogPop(1102);
7488 }
7489 
7490 // ---------------------------------------------------------------------------
7491 
7493 {
7494  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7495  ",FloatingLabelNextString" + "," + HeadCode);
7496  AnsiString RetStr = "", LocationName = "";
7497  //record action time - may be arrival, departure or event for use later (added at v2.13.2)
7498  TDateTime ActionTime = Ptr->ArrivalTime;
7499  if(ActionTime == TDateTime(-1))
7500  {
7501  ActionTime = Ptr->DepartureTime;
7502  }
7503  if(ActionTime == TDateTime(-1))
7504  {
7505  ActionTime = Ptr->EventTime;
7506  }
7507  //If ActionTime still TDateTime(-1) then the train has terminated and 'None...' will be returned
7508  //Now correct it for repeats
7509  if(ActionTime != TDateTime(-1))
7510  {
7511  ActionTime = GetTrainTime(64, ActionTime);
7512  }
7513  if(int(DelayedRandMins) > 0)
7514  {
7515  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7516  {
7517  throw Exception("Error - start entry in FloatingLabelNextString");
7518  }
7519  if(Ptr->FormatType == TimeTimeLoc)
7520  {
7521  if(TrainMode == Timetable)
7522  {
7523  if(!TrainAtLocation(0, LocationName) || (LocationName != Ptr->LocationName))
7524  // not arrived yet in tt mode
7525  {
7526  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(2, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7527  }
7528  else
7529  {
7530  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(3, Ptr->DepartureTime));
7531  }
7532  }
7533  else // TrainMode == Signaller
7534  {
7535  if(!DepartureTimeSet) // not arrived yet
7536  {
7537  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(45, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7538  }
7539  else
7540  {
7541  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(36, Ptr->DepartureTime));
7542  }
7543  }
7544  }
7545  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7546  {
7547  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(4, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7548  }
7549  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7550  {
7551  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(5, Ptr->DepartureTime));
7552  }
7553  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7554  {
7555  RetStr = "Depart " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(46, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7556  }
7557  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7558  {
7559  RetStr = "Pass " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(31, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7560  }
7561  else if(Ptr->Command == "Fns")
7562  {
7563  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(8, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7564  Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(6, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7565  RetStr = GetNewServiceDepartureInfo(0, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7566  }
7567  else if(Ptr->Command == "F-nshs")
7568  {
7569  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName;// + " at approx. " +
7570  Utilities->Format96HHMM(GetTrainTime(32, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7571  RetStr = GetNewServiceDepartureInfo(1, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7572  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7573  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7574  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7575  }
7576  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7577  {
7578  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(9, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7579  Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(7, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7580  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7581  RetStr = GetNewServiceDepartureInfo(2, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7582  }
7583  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7584  {
7585  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7586  +" at " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(8, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7587  RetStr = GetNewServiceDepartureInfo(3, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7588  }
7589  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7590  {
7591  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(10, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7592  Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(9, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7593  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7594  RetStr = GetNewServiceDepartureInfo(4, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7595  }
7596  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7597  {
7598  RetStr ="None, train terminated at " + Ptr->LocationName;
7599  }
7600  else if(Ptr->Command == "Frh")
7601  {
7602  RetStr = "None, train terminated at " + Ptr->LocationName;
7603  }
7604  else if(Ptr->Command == "Fer")
7605  {
7606  AnsiString AllowedExits = "";
7607  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(1, Ptr->ExitList, AllowedExits) + AllowedExits;
7608  }
7609  else if(Ptr->Command == "Fjo")
7610  {
7611  RetStr = "Join " + TrainController->GetRepeatHeadCode(11, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// + " at approx. " +
7612 // Utilities->Format96HHMM(GetTrainTime(11, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7613  }
7614  else if(Ptr->Command == "jbo")
7615  {
7616  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(12, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7617 // " at approx. " + Utilities->Format96HHMM(GetTrainTime(12, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7618  }
7619  else if(Ptr->Command == "fsp")
7620  {
7621  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(13, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7622 // " at approx. " + Utilities->Format96HHMM(GetTrainTime(13, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7623  }
7624  else if(Ptr->Command == "rsp")
7625  {
7626  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(14, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7627 // " at approx. " + Utilities->Format96HHMM(GetTrainTime(14, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7628  }
7629  else if(Ptr->Command == "cdt")
7630  {
7631  RetStr = "Change direction at " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(15, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7632  }
7633  else if(Ptr->Command == "dsc")
7634  {
7635  RetStr = "Change description at " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(65, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7636  }
7637  else if(Ptr->Command == "cms")
7638  {
7639  RetStr = "Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(75, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7640  }
7641  }
7642  else if(TrainController->TTClockTime > ActionTime) //condition added at v2.13.2 for trains that are delayed other than suffering a random delay
7643  {
7644  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7645  {
7646  throw Exception("Error - start entry in FloatingLabelNextString where TTClockTime > ActionTime");
7647  }
7648  if(Ptr->FormatType == TimeTimeLoc)
7649  {
7650  if(TrainMode == Timetable)
7651  {
7652  if(!TrainAtLocation(4, LocationName) || (LocationName != Ptr->LocationName))
7653  // not arrived yet in tt mode
7654  {
7655  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7656  }
7657  else
7658  {
7659  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7660  }
7661  }
7662  else // TrainMode == Signaller
7663  {
7664  if(!DepartureTimeSet) // not arrived yet
7665  {
7666  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7667  }
7668  else
7669  {
7670  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7671  }
7672  }
7673  }
7674  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7675  {
7676  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7677  }
7678  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7679  {
7680  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7681  }
7682  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7683  {
7684  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7685  }
7686  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7687  {
7688  RetStr = "Pass " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7689  }
7690  else if(Ptr->Command == "Fns")
7691  {
7692  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(60, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7693  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7694  RetStr = GetNewServiceDepartureInfo(19, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7695  }
7696  else if(Ptr->Command == "F-nshs")
7697  {
7698  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7700  RetStr = GetNewServiceDepartureInfo(20, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7701  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7702  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7703  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7704  }
7705  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7706  {
7707  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(61, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7708  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7709  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7710  RetStr = GetNewServiceDepartureInfo(21, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7711  }
7712  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7713  {
7714  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7715  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7716  RetStr = GetNewServiceDepartureInfo(22, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7717  }
7718  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7719  {
7720  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(62, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7721  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7722  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7723  RetStr = GetNewServiceDepartureInfo(23, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7724  }
7725  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7726  {
7727  RetStr ="None, train terminated at " + Ptr->LocationName;
7728  }
7729  else if(Ptr->Command == "Frh")
7730  {
7731  RetStr = "None, train terminated at " + Ptr->LocationName;
7732  }
7733  else if(Ptr->Command == "Fer")
7734  {
7735  AnsiString AllowedExits = "";
7736  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(5, Ptr->ExitList, AllowedExits) /*+ " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime)*/ + AllowedExits;
7737  }
7738  else if(Ptr->Command == "Fjo")
7739  {
7740  RetStr = "Join " + TrainController->GetRepeatHeadCode(63, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at approx. " +
7742  }
7743  else if(Ptr->Command == "jbo")
7744  {
7745  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(64, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7746  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7747  }
7748  else if(Ptr->Command == "fsp")
7749  {
7750  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(65, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7751  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7752  }
7753  else if(Ptr->Command == "rsp")
7754  {
7755  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(66, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7756  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7757  }
7758  else if(Ptr->Command == "cdt")
7759  {
7760  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7761  }
7762  else if(Ptr->Command == "dsc")
7763  {
7764  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7765  }
7766  else if(Ptr->Command == "cms")
7767  {
7768  RetStr = "Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7769  }
7770  }
7771  else //train not delayed
7772  {
7773  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7774  {
7775  throw Exception("Error - start entry in FloatingLabelNextString in final 'else'");
7776  }
7777  if(Ptr->FormatType == TimeTimeLoc)
7778  {
7779  if(TrainMode == Timetable)
7780  {
7781  if(!TrainAtLocation(3, LocationName) || (LocationName != Ptr->LocationName))
7782  // not arrived yet in tt mode
7783  {
7784  RetStr = "Arrive " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(48, Ptr->ArrivalTime));
7785  }
7786  else
7787  {
7788  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(3, Ptr->DepartureTime));
7789  }
7790  }
7791  else // TrainMode == Signaller
7792  {
7793  if(!DepartureTimeSet) // not arrived yet
7794  {
7795  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(49, Ptr->ArrivalTime));
7796  }
7797  else
7798  {
7799  RetStr = "Depart " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(36, Ptr->DepartureTime));
7800  }
7801  }
7802  }
7803  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7804  {
7805  RetStr = "Arrive " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(50, Ptr->ArrivalTime));
7806  }
7807  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7808  {
7809  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(5, Ptr->DepartureTime));
7810  }
7811  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7812  {
7813  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7814  }
7815  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7816  {
7817  RetStr = "Pass " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(52, Ptr->EventTime));
7818  }
7819  else if(Ptr->Command == "Fns")
7820  {
7821  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(53, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7822  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(53, Ptr->EventTime));
7823  RetStr = GetNewServiceDepartureInfo(10, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7824  }
7825  else if(Ptr->Command == "F-nshs")
7826  {
7827  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7829  RetStr = GetNewServiceDepartureInfo(12, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7830  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7831  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7832  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7833  }
7834  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7835  {
7836  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(54, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7837  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(55, Ptr->EventTime));
7838  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7839  RetStr = GetNewServiceDepartureInfo(14, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7840  }
7841  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7842  {
7843  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7844  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(56, Ptr->EventTime));
7845  RetStr = GetNewServiceDepartureInfo(16, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7846  }
7847  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7848  {
7849  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(55, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7850  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(57, Ptr->EventTime));
7851  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7852  RetStr = GetNewServiceDepartureInfo(18, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7853  }
7854  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7855  {
7856  RetStr ="None, train terminated at " + Ptr->LocationName;
7857  }
7858  else if(Ptr->Command == "Frh")
7859  {
7860  RetStr = "None, train terminated at " + Ptr->LocationName;
7861  }
7862  else if(Ptr->Command == "Fer")
7863  {
7864  AnsiString AllowedExits = "";
7865  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(4, Ptr->ExitList, AllowedExits) + " at approx. " + Utilities->Format96HHMM(GetTrainTime(62, Ptr->EventTime)) + AllowedExits;
7866  }
7867  else if(Ptr->Command == "Fjo")
7868  {
7869  RetStr = "Join " + TrainController->GetRepeatHeadCode(56, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at approx. " +
7871  }
7872  else if(Ptr->Command == "jbo")
7873  {
7874  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(57, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7875  " at approx. " + Utilities->Format96HHMM(GetTrainTime(59, Ptr->EventTime));
7876  }
7877  else if(Ptr->Command == "fsp")
7878  {
7879  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(58, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7880  " at approx. " + Utilities->Format96HHMM(GetTrainTime(60, Ptr->EventTime));
7881  }
7882  else if(Ptr->Command == "rsp")
7883  {
7884  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(59, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7885  " at approx. " + Utilities->Format96HHMM(GetTrainTime(61, Ptr->EventTime));
7886  }
7887  else if(Ptr->Command == "cdt")
7888  {
7889  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(63, Ptr->EventTime));
7890  }
7891  else if(Ptr->Command == "dsc")
7892  {
7893  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(66, Ptr->EventTime));
7894  }
7895  else if(Ptr->Command == "cms")
7896  {
7897  RetStr = "Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(76, Ptr->EventTime));
7898  }
7899  }
7900  Utilities->CallLogPop(1124);
7901  return(RetStr);
7902 }
7903 
7904 // ---------------------------------------------------------------------------
7905 /* as was
7906 AnsiString TTrain::FloatingLabelNextString(int Caller, TActionVectorEntry *Ptr)
7907 {
7908  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7909  ",FloatingLabelNextString" + "," + HeadCode);
7910  AnsiString RetStr = "", LocationName = "";
7911 
7912  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7913  {
7914  throw Exception("Error - start entry in FloatingLabelNextString");
7915  }
7916  if(Ptr->FormatType == TimeTimeLoc)
7917  {
7918  if(TrainMode == Timetable)
7919  {
7920  if(!TrainAtLocation(, LocationName) || (LocationName != Ptr->LocationName))
7921  // not arrived yet in tt mode
7922  {
7923  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7924  }
7925  else
7926  {
7927  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7928  }
7929  }
7930  else // TrainMode == Signaller
7931  {
7932  if(!DepartureTimeSet) // not arrived yet
7933  {
7934  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7935  }
7936  else
7937  {
7938  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7939  }
7940  }
7941  }
7942  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7943  {
7944  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7945  }
7946  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7947  {
7948  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7949  }
7950  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7951  {
7952  RetStr = "Depart " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7953  }
7954  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7955  {
7956  RetStr = "Pass " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7957  }
7958  else if(Ptr->Command == "Fns")
7959  {
7960  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7961  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7962  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7963  }
7964  else if(Ptr->Command == "F-nshs")
7965  {
7966  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at " +
7967  Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7968  RetStr = GetNewServiceDepartureInfo(, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7969  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7970  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7971  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7972  }
7973  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7974  {
7975  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7976  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7977  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7978  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7979  }
7980  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7981  {
7982  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7983  +" at " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7984  RetStr = GetNewServiceDepartureInfo(, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7985  }
7986  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7987  {
7988  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7989  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7990  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7991  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7992  }
7993  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7994  {
7995  RetStr ="None, train terminated at " + Ptr->LocationName;
7996  }
7997  else if(Ptr->Command == "Frh")
7998  {
7999  RetStr = "None, train terminated at " + Ptr->LocationName;
8000  }
8001  else if(Ptr->Command == "Fer")
8002  {
8003  AnsiString AllowedExits = "";
8004  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(, Ptr->ExitList, AllowedExits) + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime)) + AllowedExits;
8005  }
8006  else if(Ptr->Command == "Fjo")
8007  {
8008  RetStr = "Join " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at " +
8009  Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
8010  }
8011  else if(Ptr->Command == "jbo")
8012  {
8013  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
8014  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
8015  }
8016  else if(Ptr->Command == "fsp")
8017  {
8018  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
8019  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
8020  }
8021  else if(Ptr->Command == "rsp")
8022  {
8023  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
8024  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
8025  }
8026  else if(Ptr->Command == "cdt")
8027  {
8028  RetStr = "Change direction at " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
8029  }
8030  Utilities->CallLogPop();
8031  return(RetStr);
8032 }
8033 */
8034 // ---------------------------------------------------------------------------
8035 
8036 AnsiString TTrain::GetNewServiceDepartureInfo(int Caller, TActionVectorEntry *Ptr, int RptNum, TTrainDataEntry *LinkedTrainDataPtr, AnsiString RetStr, bool TimetableTime)
8037 { //last bool added at v2.13.2 so departure info adds random delay if actual rather than not timetable time required
8038  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) + ","
8039  + AnsiString(RptNum) + ",GetNewServiceDepartureInfo," + HeadCode);
8040  AnsiString DepTime = "", EventTime = "";
8041  bool CDTFlag = false; //reports if train changes direction before departs
8042  TActionVector NewServiceAV = LinkedTrainDataPtr->ActionVector;
8043  AnsiString CurrentLocation = NewServiceAV.at(0).LocationName; //added at v2.12.0 to show departure direction
8044  AnsiString TowardsLocation = ""; //added at v2.12.0 to show departure direction
8045  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++) //added at v2.12.0 to obtain departure direction
8046  {
8047  if((AVI->LocationName != CurrentLocation) && (AVI->LocationName != "") && (TowardsLocation == ""))
8048  {
8049  TowardsLocation = AVI->LocationName;
8050  }
8051  else if((AVI->Command == "Fer") && (TowardsLocation == "") && !AVI->ExitList.empty())
8052  {
8053  TTrackElement TE = Track->TrackElementAt(1452, (AVI->ExitList.front()));
8054  if(TE.ActiveTrackElementName != "")
8055  {
8056  TowardsLocation = TE.ActiveTrackElementName;
8057  }
8058  else
8059  {
8060  TowardsLocation = AnsiString("track element ID ") + TE.ElementID;
8061  }
8062  }
8063  }
8064 
8065  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++)
8066  {
8067  if(AVI->Command == "cdt")
8068  {
8069  CDTFlag = !CDTFlag; //toggles flag - allows for there being more than one cdt before departure
8070  continue;
8071  }
8072  if((AVI->Command == "fsp") || (AVI->Command == "rsp"))
8073  {
8074  TDateTime TTTime = TrainController->GetControllerTrainTime(19, AVI->EventTime, RptNum, IncrementalMinutes);
8075  if((DelayedRandMins >= 1) && !TimetableTime)
8076  {
8077  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8078  }
8079  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8080  {
8082  }
8083  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8084  {
8085  EventTime = Utilities->Format96HHMM(TTTime);
8086  }
8087  RetStr += "\nNew service splits at approx. " + EventTime;
8088  Utilities->CallLogPop(2234);
8089  return(RetStr);
8090  }
8091  if(AVI->Command == "jbo") //added at v2.15.0
8092  {
8093  TDateTime TTTime = TrainController->GetControllerTrainTime(28, AVI->EventTime, RptNum, IncrementalMinutes);
8094  if((DelayedRandMins >= 1) && !TimetableTime)
8095  {
8096  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8097  }
8098  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8099  {
8101  }
8102  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8103  {
8104  EventTime = Utilities->Format96HHMM(TTTime);
8105  }
8106  RetStr += "\nNew service joined by " + TrainController->GetRepeatHeadCode(68, AVI->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at approx. " + EventTime;
8107  Utilities->CallLogPop(2595); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
8108  return(RetStr);
8109  }
8110  if((AVI->Command == "Fns") || (AVI->Command == "F-nshs") || (AVI->Command == "Fns-sh")) //added at v2.15.0
8111  {
8112  TDateTime TTTime = TrainController->GetControllerTrainTime(29, AVI->EventTime, RptNum, IncrementalMinutes);
8113  if((DelayedRandMins >= 1) && !TimetableTime)
8114  {
8115  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8116  }
8117  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8118  {
8120  }
8121  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8122  {
8123  EventTime = Utilities->Format96HHMM(TTTime);
8124  }
8125  RetStr += "\nNew service finishes and forms another new service at approx. " + EventTime;
8126  Utilities->CallLogPop(2615);
8127  return(RetStr);
8128  }
8129  if(AVI->Command == "Fjo") //added at v2.15.0
8130  {
8131  TDateTime TTTime = TrainController->GetControllerTrainTime(26, AVI->EventTime, RptNum, IncrementalMinutes);
8132  if((DelayedRandMins >= 1) && !TimetableTime)
8133  {
8134  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8135  }
8136  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8137  {
8139  }
8140  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8141  {
8142  EventTime = Utilities->Format96HHMM(TTTime);
8143  }
8144  RetStr += "\nNew service finishes and joins " + TrainController->GetRepeatHeadCode(69, AVI->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at approx. " + EventTime;
8145  Utilities->CallLogPop(2605); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
8146  return(RetStr);
8147  }
8148  if(AVI->Command == "Frh") //added at v2.15.0
8149  {
8150  RetStr += "\nNew service finishes and remains at the location.";
8151  Utilities->CallLogPop(2606);
8152  return(RetStr);
8153  }
8154  if((AVI->FormatType == TimeLoc) && (AVI->DepartureTime > TDateTime(-1))) //departure time set
8155  {
8156  if(TimetableTime) //don't add random delay
8157  {
8158  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(17, AVI->DepartureTime, RptNum, IncrementalMinutes));
8159  if(CDTFlag)
8160  {
8161  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8162  {
8163  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at " + DepTime;
8164  }
8165  else
8166  {
8167  RetStr += "\nNew service changes direction then departs at " + DepTime;
8168  }
8169  }
8170  else
8171  {
8172  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8173  {
8174  RetStr += "\nNew service departs towards " + TowardsLocation + " at " + DepTime;
8175  }
8176  else
8177  {
8178  RetStr += "\nNew service departs at " + DepTime;
8179  }
8180  }
8181  }
8182  else if(DelayedRandMins >= 1)//add random delay
8183  {
8184  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(24, AVI->DepartureTime + TDateTime(DelayedRandMins/1440), RptNum, IncrementalMinutes));
8185  if(CDTFlag)
8186  {
8187  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8188  {
8189  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at approx. " + DepTime;
8190  }
8191  else
8192  {
8193  RetStr += "\nNew service changes direction then departs at approx. " + DepTime;
8194  }
8195  }
8196  else
8197  {
8198  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8199  {
8200  RetStr += "\nNew service departs towards " + TowardsLocation + " at approx. " + DepTime;
8201  }
8202  else
8203  {
8204  RetStr += "\nNew service departs at approx. " + DepTime;
8205  }
8206  }
8207  }
8208  else //no random delay but may be delayed for other reasons
8209  {
8210  TDateTime TTTime = TrainController->GetControllerTrainTime(25, AVI->DepartureTime, RptNum, IncrementalMinutes);
8211  if(TrainController->TTClockTime > TTTime)
8212  {
8214  }
8215  else
8216  {
8217  DepTime = Utilities->Format96HHMM(TTTime);
8218  }
8219  if(CDTFlag)
8220  {
8221  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8222  {
8223  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at approx. " + DepTime;
8224  }
8225  else
8226  {
8227  RetStr += "\nNew service changes direction then departs at approx. " + DepTime;
8228  }
8229  }
8230  else
8231  {
8232  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8233  {
8234  RetStr += "\nNew service departs towards " + TowardsLocation + " at approx. " + DepTime;
8235  }
8236  else
8237  {
8238  RetStr += "\nNew service departs at approx. " + DepTime;
8239  }
8240  }
8241  }
8242  Utilities->CallLogPop(2236);
8243  return(RetStr);
8244  }
8245  }
8246  Utilities->CallLogPop(2208);
8247  return(RetStr); //if reach here then RetStr doesn't change
8248 }
8249 
8250 // ---------------------------------------------------------------------------
8251 
8253 // Enter with Ptr pointing to first action to be listed (i.e. next action)
8254 // If there are actions to be skipped but a departure is awaited (SkippedDeparture = true) then after the departure Ptr moves forward by SkipPtrValue
8255 {
8256  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
8257  ",FloatingTimetableString" + "," + HeadCode);
8258  AnsiString RetStr = "", PartStr = "";
8259  int Count = 0;
8260  bool SkipDep = false, SkipDepActedOn = false; //SkipDepActedOn ensures only one SkipDep acted on
8261  AnsiString LocName = Ptr->LocationName;
8262  AnsiString MinMinsString = "";
8263 
8264  if((Ptr->Command != "") && (Ptr->Command[1] == 'S') && (TrainMode == Timetable))
8265  // can start in signaller control so exclude this
8266  {
8267  throw Exception("Error - start entry in FloatingTimetableString");
8268  }
8269  TActionVectorEntry *EntryPtr = Ptr; //used in TimeTimeLoc check later to distinguish between starting at the location and later same location entries
8270  bool FirstPass = true; // different first TimeTimeLoc display if in signaller control
8271  Ptr--; // because incremented at start of loop
8272 
8273  do
8274  {
8275  Ptr++;
8276  AnsiString TrainLoc = ""; //moved to here from below at v2.20.1 when added last 2 conditions in first PassTime check below (see Albie Vowles' email of 25/07/24)
8277  if((Ptr->FormatType == Repeat) || TimetableFinished)
8278  {
8279  break;
8280  }
8281  if((Ptr->FormatType == TimeTimeLoc) && FirstPass)
8282  {
8283 // AnsiString TrainLoc = ""; moved from here at v2.20.1 - see above
8284  if(TrainMode == Timetable)
8285  {
8286  if(TrainAtLocation(1, TrainLoc) && (TrainLoc == Ptr->LocationName) && (Ptr == EntryPtr)) //added '&& (Ptr == EntryPtr)' at v2.6.0 when allow multiple same location entries
8287  {
8288  PartStr = Utilities->Format96HHMM(GetTrainTime(33, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName; //no MDT for departure
8289  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8290  {
8291  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8292  }
8293  }
8294  else if(Ptr->ArrivalTime == Ptr->DepartureTime) //arrive & dep shown in single entry
8295  {
8296  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8297  {
8298  double MDTdouble = Ptr->MinDwellTime / 60;
8299  double MDT = int(MDTdouble * 10);
8300  MDT = MDT / 10;
8301  MinMinsString = "mins";
8302  if((MDT < 1.1) && (MDT > 0.9))
8303  {
8304  MinMinsString = "min";
8305  }
8306  PartStr = Utilities->Format96HHMM(GetTrainTime(34, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
8307  }
8308  else
8309  {
8310  PartStr = Utilities->Format96HHMM(GetTrainTime(89, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
8311  }
8312  }
8313  else
8314  {
8315  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8316  {
8317  double MDTdouble = Ptr->MinDwellTime / 60;
8318  double MDT = int(MDTdouble * 10);
8319  MDT = MDT / 10;
8320  MinMinsString = "mins";
8321  if((MDT < 1.1) && (MDT > 0.9))
8322  {
8323  MinMinsString = "min";
8324  }
8325  PartStr = Utilities->Format96HHMM(GetTrainTime(16, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')' + '\n' +
8326  Utilities->Format96HHMM(GetTrainTime(17, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8327  }
8328  else
8329  {
8330  PartStr = Utilities->Format96HHMM(GetTrainTime(80, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
8331  Utilities->Format96HHMM(GetTrainTime(84, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8332  }
8333  Count++; // because there are 2 entries
8334  }
8335  }
8336  else // TrainMode == Signaller
8337  {
8338  if(DepartureTimeSet)
8339  {
8340  PartStr = Utilities->Format96HHMM(GetTrainTime(37, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8341  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8342  {
8343  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8344  }
8345  }
8346  else if(Ptr->ArrivalTime == Ptr->DepartureTime) //arrive & dep shown in single entry
8347  {
8348  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8349  {
8350  double MDTdouble = Ptr->MinDwellTime / 60;
8351  double MDT = int(MDTdouble * 10);
8352  MDT = MDT / 10;
8353  MinMinsString = "mins";
8354  if((MDT < 1.1) && (MDT > 0.9))
8355  {
8356  MinMinsString = "min";
8357  }
8358  PartStr = Utilities->Format96HHMM(GetTrainTime(90, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
8359  }
8360  else
8361  {
8362  PartStr = Utilities->Format96HHMM(GetTrainTime(91, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
8363  }
8364  }
8365  else
8366  {
8367  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8368  {
8369  double MDTdouble = Ptr->MinDwellTime / 60;
8370  double MDT = int(MDTdouble * 10);
8371  MDT = MDT / 10;
8372  MinMinsString = "mins";
8373  if((MDT < 1.1) && (MDT > 0.9))
8374  {
8375  MinMinsString = "min";
8376  }
8377  PartStr = Utilities->Format96HHMM(GetTrainTime(94, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')' + '\n' +
8378  Utilities->Format96HHMM(GetTrainTime(96, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8379  }
8380  else
8381  {
8382  PartStr = Utilities->Format96HHMM(GetTrainTime(98, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
8383  Utilities->Format96HHMM(GetTrainTime(100, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8384  }
8385  Count++; // because there are 2 entries
8386  }
8387  }
8388  }
8389  else if((Ptr->FormatType == TimeTimeLoc) && !FirstPass)
8390  {
8391  AnsiString TrainLoc = "";
8392  if((TrainAtLocation(2, TrainLoc)) && (TrainLoc == Ptr->LocationName) && (Ptr == EntryPtr)) //added '&& (Ptr == EntryPtr)' at v2.6.0 when allow multiple same location entries
8393  {
8394  PartStr = Utilities->Format96HHMM(GetTrainTime(41, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8395  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8396  {
8397  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8398  }
8399  }
8400  else if(Ptr->ArrivalTime == Ptr->DepartureTime) //arrive & dep shown in single entry
8401  {
8402  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8403  {
8404  double MDTdouble = Ptr->MinDwellTime / 60;
8405  double MDT = int(MDTdouble * 10);
8406  MDT = MDT / 10;
8407  MinMinsString = "mins";
8408  if((MDT < 1.1) && (MDT > 0.9))
8409  {
8410  MinMinsString = "min";
8411  }
8412  PartStr = Utilities->Format96HHMM(GetTrainTime(92, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
8413  }
8414  else
8415  {
8416  PartStr = Utilities->Format96HHMM(GetTrainTime(93, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
8417  }
8418  }
8419  else
8420  {
8421  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8422  {
8423  double MDTdouble = Ptr->MinDwellTime / 60;
8424  double MDT = int(MDTdouble * 10);
8425  MDT = MDT / 10;
8426  MinMinsString = "mins";
8427  if((MDT < 1.1) && (MDT > 0.9))
8428  {
8429  MinMinsString = "min";
8430  }
8431  PartStr = Utilities->Format96HHMM(GetTrainTime(95, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')' + '\n' +
8432  Utilities->Format96HHMM(GetTrainTime(97, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8433  }
8434  else
8435  {
8436  PartStr = Utilities->Format96HHMM(GetTrainTime(99, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
8437  Utilities->Format96HHMM(GetTrainTime(101, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8438  }
8439  Count++; // because there are 2 entries
8440  }
8441  }
8442  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1))) //arrival
8443  {
8444  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
8445  {
8446  double MDTdouble = Ptr->MinDwellTime / 60;
8447  double MDT = int(MDTdouble * 10);
8448  MDT = MDT / 10;
8449  MinMinsString = "mins";
8450  if((MDT < 1.1) && (MDT > 0.9))
8451  {
8452  MinMinsString = "min";
8453  }
8454  PartStr = Utilities->Format96HHMM(GetTrainTime(88, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
8455  }
8456  else
8457  {
8458  PartStr = Utilities->Format96HHMM(GetTrainTime(83, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName;
8459  }
8460  }
8461  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1))) //departure
8462  {
8463  PartStr = Utilities->Format96HHMM(GetTrainTime(19, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8464  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8465  {
8466  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8467  }
8468  }
8469  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture && (TrainAtLocation(5, TrainLoc)) && (TrainLoc == Ptr->LocationName)) //added at v2.12.0 for becoming new service early (see BecomeNewService)
8470  { //note that TreatPassAsTimeLocDeparture can't be set if have SkippedDeparture
8471  //added last 2 condits at v2.20.1 as otherwise all later passes listed as departures (see Albie Vowles' email of 25/07/24)
8472  PartStr = Utilities->Format96HHMM(GetTrainTime(47, Ptr->EventTime)) + ": Depart from " + Ptr->LocationName;
8473  }
8474  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
8475  {
8476  PartStr = Utilities->Format96HHMM(GetTrainTime(30, Ptr->EventTime)) + ": Pass " + Ptr->LocationName;
8477  }
8478  else if(Ptr->Command == "Fns")
8479  {
8480  PartStr = Utilities->Format96HHMM(GetTrainTime(20, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(15,
8481  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8482  PartStr = GetNewServiceDepartureInfo(5, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to PartStr
8483  }
8484  else if(Ptr->Command == "F-nshs")
8485  {
8486  PartStr = Utilities->Format96HHMM(GetTrainTime(35, Ptr->EventTime)) + ": Form new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " +
8487  Ptr->LocationName;
8488  PartStr = GetNewServiceDepartureInfo(6, Ptr, 0, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8489  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
8490  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
8491  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
8492  }
8493  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not the last repeat number
8494  {
8495  PartStr = Utilities->Format96HHMM(GetTrainTime(21, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(16,
8496  Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " + Ptr->LocationName;
8497  // use RepeatNumber+1 because it's the repeat number of the NEXT shuttle service that is relevant
8498  PartStr = GetNewServiceDepartureInfo(7, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8499  }
8500  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
8501  {
8502  PartStr = Utilities->Format96HHMM(GetTrainTime(22, Ptr->EventTime)) + ": Form new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
8503  +" at " + Ptr->LocationName;
8504  PartStr = GetNewServiceDepartureInfo(8, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8505  }
8506  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not the last repeat number
8507  {
8508  PartStr = Utilities->Format96HHMM(GetTrainTime(23, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(17,
8509  Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " + Ptr->LocationName;
8510  // use RepeatNumber+1 because it's the repeat number of the NEXT shuttle service that is relevant
8511  PartStr = GetNewServiceDepartureInfo(9, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8512  }
8513  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
8514  {
8515  PartStr = "Terminate at " + Ptr->LocationName;
8516  }
8517  else if(Ptr->Command == "Frh")
8518  {
8519  PartStr = "Terminate at " + Ptr->LocationName;
8520  }
8521  else if(Ptr->Command == "Fer")
8522  {
8523  AnsiString AllowedExits = "";
8524  PartStr = Utilities->Format96HHMM(GetTrainTime(24, Ptr->EventTime)) + ": Exit railway" + TrainController->GetExitLocationAndAt(2, Ptr->ExitList, AllowedExits) + AllowedExits;
8525  }
8526  else if(Ptr->Command == "Fjo")
8527  {
8528  PartStr = Utilities->Format96HHMM(GetTrainTime(25, Ptr->EventTime)) + ": Join " + TrainController->GetRepeatHeadCode(18, Ptr->OtherHeadCode,
8529  RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8530  }
8531  else if(Ptr->Command == "jbo")
8532  {
8533  PartStr = Utilities->Format96HHMM(GetTrainTime(26, Ptr->EventTime)) + ": Joined by " + TrainController->GetRepeatHeadCode(19, Ptr->OtherHeadCode,
8534  RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8535  }
8536  else if(Ptr->Command == "fsp")
8537  {
8538  PartStr = Utilities->Format96HHMM(GetTrainTime(27, Ptr->EventTime)) + ": Front split to " + TrainController->GetRepeatHeadCode(20,
8539  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8540  if(Ptr->SplitDistribution != "")
8541  {
8542  PartStr+= ", split mass%-Power% = " + Ptr->SplitDistribution;
8543  }
8544  else
8545  {
8546  PartStr+= ", split mass%-Power% = 50-50";
8547  }
8548  }
8549  else if(Ptr->Command == "rsp")
8550  {
8551  PartStr = Utilities->Format96HHMM(GetTrainTime(28, Ptr->EventTime)) + ": Rear split to " + TrainController->GetRepeatHeadCode(21,
8552  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8553  if(Ptr->SplitDistribution != "")
8554  {
8555  PartStr+= ", split mass%-Power% = " + Ptr->SplitDistribution;
8556  }
8557  else
8558  {
8559  PartStr+= ", split mass%-Power% = 50-50";
8560  }
8561  }
8562  else if(Ptr->Command == "cdt")
8563  {
8564  PartStr = Utilities->Format96HHMM(GetTrainTime(29, Ptr->EventTime)) + ": Change direction at " + Ptr->LocationName;
8565  }
8566  else if(Ptr->Command == "dsc")
8567  {
8568  PartStr = Utilities->Format96HHMM(GetTrainTime(67, Ptr->EventTime)) + ": Change description at " + Ptr->LocationName;
8569  }
8570  else if(Ptr->Command == "cms")
8571  {
8572  PartStr = Utilities->Format96HHMM(GetTrainTime(77, Ptr->EventTime)) + ": Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName;
8573  }
8574  if(RetStr != "")
8575  {
8576  RetStr = RetStr + '\n' + PartStr;
8577  }
8578  else
8579  {
8580  RetStr = PartStr;
8581  }
8582  FirstPass = false;
8583  Count++;
8584 
8585  if(SkipDep)
8586  {
8587  Ptr = &(TrainDataEntryPtr->ActionVector.at(0)) + SkipPtrValue;
8588  Ptr--; //it is incremented at the start of the next loop
8589  SkipDep = false;
8590  SkipDepActedOn = true;
8591  }
8592  }
8593  while(!TimetableFinished && (Count < 32) && ((Ptr->Command == "") || ((Ptr->Command != "") && (Ptr->Command[1] != 'F'))));
8594  // limit of 32 allows a max of 34 entries (33 + 1 for the new service departure time) (may have gone from 32 to 34 because of a TimeTimeLoc), which with track and
8595  // train status gives a max of 48 lines, at 13 pixels each, = 624 pixels & screen height has 641 so will fit comfortably. Also 34 timetable entries is as far
8596  // forward as anyone should wish to see without looking at the full timetable
8597  if(TimetableFinished)
8598  {
8599  if(TrainMode == Timetable)
8600  {
8601  RetStr = "Timetable finished";
8602  }
8603  else
8604  {
8605  RetStr = "No timetable";
8606  }
8607  }
8608  Utilities->CallLogPop(1125);
8609  return("Timetable:\n" + RetStr);
8610 }
8611 
8612 // ---------------------------------------------------------------------------
8613 
8614 void TTrain::SaveOneSessionTrain(int Caller, std::ofstream &OutFile)
8615 {
8616  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveOneSessionTrain" + "," + HeadCode);
8617  Utilities->SaveFileString(OutFile, HeadCode);
8620  Utilities->SaveFileInt(OutFile, StartSpeed);
8623  Utilities->SaveFileInt(OutFile, RepeatNumber);
8626  Utilities->SaveFileInt(OutFile, Mass);
8629  Utilities->SaveFileDouble(OutFile, EntrySpeed);
8636  Utilities->SaveFileDouble(OutFile, BrakeRate);
8640  Utilities->SaveFileDouble(OutFile, double(EntryTime));
8641  Utilities->SaveFileDouble(OutFile, double(ExitTimeHalf));
8642  Utilities->SaveFileDouble(OutFile, double(ExitTimeFull));
8643  Utilities->SaveFileDouble(OutFile, double(ReleaseTime));
8644  Utilities->SaveFileDouble(OutFile, double(TRSTime));
8645  Utilities->SaveFileDouble(OutFile, double(LastActionTime));
8649  Utilities->SaveFileInt(OutFile, (short)TrainMode);
8654  Utilities->SaveFileBool(OutFile, Derailed);
8656  Utilities->SaveFileBool(OutFile, Crashed);
8663  Utilities->SaveFileBool(OutFile, NotInService);
8664  Utilities->SaveFileBool(OutFile, Plotted);
8665  Utilities->SaveFileBool(OutFile, TrainGone);
8666  Utilities->SaveFileBool(OutFile, SPADFlag);
8668  Utilities->SaveFileInt(OutFile, HOffset[0]);
8669  Utilities->SaveFileInt(OutFile, HOffset[1]);
8670  Utilities->SaveFileInt(OutFile, HOffset[2]);
8671  Utilities->SaveFileInt(OutFile, HOffset[3]);
8672  Utilities->SaveFileInt(OutFile, VOffset[0]);
8673  Utilities->SaveFileInt(OutFile, VOffset[1]);
8674  Utilities->SaveFileInt(OutFile, VOffset[2]);
8675  Utilities->SaveFileInt(OutFile, VOffset[3]);
8676  Utilities->SaveFileInt(OutFile, PlotElement[0]);
8677  Utilities->SaveFileInt(OutFile, PlotElement[1]);
8678  Utilities->SaveFileInt(OutFile, PlotElement[2]);
8679  Utilities->SaveFileInt(OutFile, PlotElement[3]);
8680  Utilities->SaveFileInt(OutFile, PlotEntryPos[0]);
8681  Utilities->SaveFileInt(OutFile, PlotEntryPos[1]);
8682  Utilities->SaveFileInt(OutFile, PlotEntryPos[2]);
8683  Utilities->SaveFileInt(OutFile, PlotEntryPos[3]);
8685  Utilities->SaveFileInt(OutFile, (short)Straddle);
8686  Utilities->SaveFileInt(OutFile, NextTrainID);
8687  Utilities->SaveFileInt(OutFile, TrainID);
8688  Utilities->SaveFileInt(OutFile, LeadElement);
8689  Utilities->SaveFileInt(OutFile, LeadEntryPos);
8690  Utilities->SaveFileInt(OutFile, LeadExitPos);
8691  Utilities->SaveFileInt(OutFile, MidElement);
8692  Utilities->SaveFileInt(OutFile, MidEntryPos);
8693  Utilities->SaveFileInt(OutFile, MidExitPos);
8694  Utilities->SaveFileInt(OutFile, LagElement);
8695  Utilities->SaveFileInt(OutFile, LagEntryPos);
8696  Utilities->SaveFileInt(OutFile, LagExitPos);
8697  int ColourNumber;
8698 
8700  {
8701  ColourNumber = 0;
8702  }
8704  {
8705  ColourNumber = 1;
8706  }
8708  {
8709  ColourNumber = 2;
8710  }
8712  {
8713  ColourNumber = 3;
8714  }
8716  {
8717  ColourNumber = 4;
8718  }
8720  {
8721  ColourNumber = 5;
8722  }
8724  {
8725  ColourNumber = 6;
8726  }
8728  {
8729  ColourNumber = 7;
8730  }
8732  {
8733  ColourNumber = 8;
8734  }
8736  {
8737  ColourNumber = 9;
8738  }
8740  {
8741  ColourNumber = 10;
8742  }
8744  {
8745  ColourNumber = 11;
8746  }
8748  {
8749  ColourNumber = 12;
8750  }
8751  else if(BackgroundColour == clTRSBackground)
8752  {
8753  ColourNumber = 13;
8754  }
8756  {
8757  ColourNumber = 14; // added at v2.4.0
8758  }
8759  Utilities->SaveFileInt(OutFile, ColourNumber);
8760 
8761  // additional data
8762  bool ForwardHeadCode;
8763 
8764  if(HeadCodePosition[3] == HeadCodeGrPtr[3])
8765  {
8766  ForwardHeadCode = true;
8767  }
8768  // can't use 'if(HeadCodePosition[0] == HeadCodeGrPtr[0])' as HeadCodePosition[0] is set to FrontCodePtr
8769  else
8770  {
8771  ForwardHeadCode = false;
8772  }
8773  Utilities->SaveFileBool(OutFile, ForwardHeadCode);
8774 
8775  int TrainDataEntryValue = TrainDataEntryPtr - &(TrainController->TrainDataVector.at(0));
8776 
8777  Utilities->SaveFileInt(OutFile, TrainDataEntryValue);
8778  int ActionVectorEntryValue = ActionVectorEntryPtr - &(TrainDataEntryPtr->ActionVector.at(0));
8779 
8780  Utilities->SaveFileInt(OutFile, ActionVectorEntryValue);
8781  // now the marker comes next which was ****** originally but used for RestoreTimetableLocation as well some time ago (came before the asterisks)
8782  // but at v2.4.0 need to include StoppedWithoutPower, while keeping length of marker at 6, because that is tested in earlier versions
8783  // so use the last asterisk position for this - 0 for false & 1 for true
8784  // note that failed train data is handled in InterfaceUnit.cpp & stored after the performance file
8785  AnsiString Marker;
8786 
8788  {
8789  Marker = "*****1";
8790  }
8791  else
8792  {
8793  Marker = "*****0";
8794  }
8795  if(RestoreTimetableLocation == "")
8796  {
8797  Utilities->SaveFileString(OutFile, Marker);
8798  }
8799  else
8800  {
8801  AnsiString CombinedString = RestoreTimetableLocation + Marker;
8802  Utilities->SaveFileString(OutFile, CombinedString);
8803  // RestoreTimetableLocation + marker
8804  }
8805  // Note: including RestoreTimetableLocation with the marker is to correct an oversight - it should have been saved earlier
8806  Utilities->CallLogPop(1457);
8807 }
8808 
8809 // ---------------------------------------------------------------------------
8810 
8811 void TTrain::LoadOneSessionTrain(int Caller, std::ifstream &InFile)
8812 {
8813  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadOneSessionTrain"); // don't have headcode yet
8814  HeadCode = Utilities->LoadFileString(InFile);
8817  StartSpeed = Utilities->LoadFileInt(InFile);
8819  if(SignallerMaxSpeed < 10)
8820  {
8821  SignallerMaxSpeed = 10; // added at v0.6 to avoid low max speeds
8822  }
8824  RepeatNumber = Utilities->LoadFileInt(InFile);
8827  Mass = Utilities->LoadFileInt(InFile);
8830  {
8832  }
8833  // above added at v2.1.0 for legacy session files where value may not have been limited
8835  EntrySpeed = Utilities->LoadFileDouble(InFile);
8839  if(TimetableMaxRunningSpeed < 10)
8840  {
8841  TimetableMaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
8842  }
8844  if(MaxRunningSpeed < 10)
8845  {
8846  MaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
8847  }
8850  BrakeRate = Utilities->LoadFileDouble(InFile);
8854  EntryTime = TDateTime(Utilities->LoadFileDouble(InFile));
8855  ExitTimeHalf = TDateTime(Utilities->LoadFileDouble(InFile));
8856  ExitTimeFull = TDateTime(Utilities->LoadFileDouble(InFile));
8857  ReleaseTime = TDateTime(Utilities->LoadFileDouble(InFile));
8858  TRSTime = TDateTime(Utilities->LoadFileDouble(InFile));
8859  LastActionTime = TDateTime(Utilities->LoadFileDouble(InFile));
8868  Derailed = Utilities->LoadFileBool(InFile);
8870  Crashed = Utilities->LoadFileBool(InFile);
8877  NotInService = Utilities->LoadFileBool(InFile);
8878  Plotted = Utilities->LoadFileBool(InFile);
8879  TrainGone = Utilities->LoadFileBool(InFile);
8880  SPADFlag = Utilities->LoadFileBool(InFile);
8882  HOffset[0] = Utilities->LoadFileInt(InFile);
8883  HOffset[1] = Utilities->LoadFileInt(InFile);
8884  HOffset[2] = Utilities->LoadFileInt(InFile);
8885  HOffset[3] = Utilities->LoadFileInt(InFile);
8886  VOffset[0] = Utilities->LoadFileInt(InFile);
8887  VOffset[1] = Utilities->LoadFileInt(InFile);
8888  VOffset[2] = Utilities->LoadFileInt(InFile);
8889  VOffset[3] = Utilities->LoadFileInt(InFile);
8890  PlotElement[0] = Utilities->LoadFileInt(InFile);
8891  PlotElement[1] = Utilities->LoadFileInt(InFile);
8892  PlotElement[2] = Utilities->LoadFileInt(InFile);
8893  PlotElement[3] = Utilities->LoadFileInt(InFile);
8894  PlotEntryPos[0] = Utilities->LoadFileInt(InFile);
8895  PlotEntryPos[1] = Utilities->LoadFileInt(InFile);
8896  PlotEntryPos[2] = Utilities->LoadFileInt(InFile);
8897  PlotEntryPos[3] = Utilities->LoadFileInt(InFile);
8899  Straddle = (TStraddle)(Utilities->LoadFileInt(InFile));
8900  NextTrainID = Utilities->LoadFileInt(InFile);
8901  // will be same for all but best to save all anyway
8902  TrainID = Utilities->LoadFileInt(InFile);
8903  LeadElement = Utilities->LoadFileInt(InFile);
8904  LeadEntryPos = Utilities->LoadFileInt(InFile);
8905  LeadExitPos = Utilities->LoadFileInt(InFile);
8906  MidElement = Utilities->LoadFileInt(InFile);
8907  MidEntryPos = Utilities->LoadFileInt(InFile);
8908  MidExitPos = Utilities->LoadFileInt(InFile);
8909  LagElement = Utilities->LoadFileInt(InFile);
8910  LagEntryPos = Utilities->LoadFileInt(InFile);
8911  LagExitPos = Utilities->LoadFileInt(InFile);
8912  int ColourNumber = TColor(Utilities->LoadFileInt(InFile));
8913 
8914  if(ColourNumber == 0)
8915  {
8917  }
8918  else if(ColourNumber == 1)
8919  {
8921  }
8922  else if(ColourNumber == 2)
8923  {
8925  }
8926  else if(ColourNumber == 3)
8927  {
8929  }
8930  else if(ColourNumber == 4)
8931  {
8933  }
8934  else if(ColourNumber == 5)
8935  {
8937  }
8938  else if(ColourNumber == 6)
8939  {
8941  }
8942  else if(ColourNumber == 7)
8943  {
8945  }
8946  else if(ColourNumber == 8)
8947  {
8949  }
8950  else if(ColourNumber == 9)
8951  {
8953  }
8954  else if(ColourNumber == 10)
8955  {
8957  }
8958  else if(ColourNumber == 11)
8959  {
8961  }
8962  else if(ColourNumber == 12)
8963  {
8965  }
8966  else if(ColourNumber == 13)
8967  {
8969  }
8970  else if(ColourNumber == 14)
8971  {
8972  BackgroundColour = clTrainFailedBackground; // added at v2.4.0
8973 
8974  }
8975  // additional data
8977  // sets the BackgroundColour to the loaded value
8978  bool ForwardHeadCode = Utilities->LoadFileBool(InFile);
8979 
8980  if(ForwardHeadCode)
8981  {
8982  for(int x = 0; x < 4; x++)
8983  {
8985  }
8986  }
8987  else
8988  {
8989  for(int x = 0; x < 4; x++)
8990  {
8991  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
8992  }
8993  }
8994  // if crashed & in timetable mode then change FrontCodePtr to black, if in signaller mode then change to blue whether crashed or not
8995  if(TrainMode == Timetable)
8996  {
8997  if(Crashed)
8998  {
9000  }
9001  else
9002  {
9004  }
9005  }
9006  else
9007  {
9009  }
9011  // pick up background bitmaps, none if MidLag as no train plotted - entering at continuation
9012  if(Straddle == LeadMid)
9013  {
9014  if(LeadElement > -1)
9015  {
9017  }
9018  if(LeadElement > -1)
9019  {
9021  }
9022  if(MidElement > -1)
9023  {
9025  }
9026  if(MidElement > -1)
9027  {
9029  }
9030  }
9031  else if(Straddle == LeadMidLag)
9032  {
9033  if(LeadElement > -1)
9034  {
9036  }
9037  if(MidElement > -1)
9038  {
9040  }
9041  if(MidElement > -1)
9042  {
9044  }
9045  if(LagElement > -1)
9046  {
9048  }
9049  }
9050  int TrainDataEntryValue = Utilities->LoadFileInt(InFile);
9051 
9052  TrainDataEntryPtr = &(TrainController->TrainDataVector.at(0)) + TrainDataEntryValue;
9053  int ActionVectorEntryValue = Utilities->LoadFileInt(InFile);
9054 
9055  ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0)) + ActionVectorEntryValue;
9056 
9057  // need to set the TrainID if arriving at a continuation but hasn't been plotted yet
9058  if(LeadElement > -1)
9059  // need to include this in case train exiting & no lead element
9060  {
9062  {
9063  Track->TrackElementAt(668, LeadElement).TrainIDOnElement = TrainID; // no need to stop gap flashing if a continuation
9064  }
9065  }
9066  AValue = sqrt(2 * PowerAtRail / Mass);
9067 
9068  AnsiString LocationAndMarker = Utilities->LoadFileString(InFile);
9069 
9070  // possible RestoreTimetableLocation + Marker, where Marker is
9071  // "*****0" for !StoppedWithoutPower and "*****1" otherwise (from v2.4.0)
9072  // Note: including RestoreTimetableLocation with the marker is to correct an oversight - RestoreTimetableLocation should have been saved earlier
9073  // added at beta v0.2e
9074  if((LocationAndMarker[1] != '*') && (LocationAndMarker.Length() > 6))
9075  // name not allowed to include the '*' character
9076  {
9077  AnsiString Location = LocationAndMarker.SubString(1, LocationAndMarker.Length() - 6);
9078  bool GiveMessagesFalse = false;
9079  bool CheckLocationsExistInRailwayTrue = true;
9080  if(TrainController->CheckLocationValidity(3, Location, GiveMessagesFalse, CheckLocationsExistInRailwayTrue))
9081  {
9082  // otherwise take no action
9083  RestoreTimetableLocation = Location;
9084  }
9085  }
9086  AnsiString Marker = LocationAndMarker.SubString(LocationAndMarker.Length() - 5, 6);
9087 
9088  StoppedWithoutPower = false;
9089  if(Marker[6] == '1')
9090  {
9091  StoppedWithoutPower = true;
9092  }
9093  Utilities->CallLogPop(1458);
9094 }
9095 
9096 // ---------------------------------------------------------------------------
9097 
9098 bool TTrain::CheckOneSessionTrain(std::ifstream &InFile)
9099 {
9101  {
9102  return(false); // HeadCode
9103 
9104  }
9105  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
9106  {
9107  return(false); // RearStartElement
9108 
9109  }
9110  if(!Utilities->CheckFileInt(InFile, 0, 3))
9111  {
9112  return(false); // RearStartExitPos
9113 
9114  }
9115  if(!Utilities->CheckFileInt(InFile, 0, MaximumSpeedLimit))
9116  {
9117  return(false); // StartSpeed
9118 
9119  }
9120  if(!Utilities->CheckFileInt(InFile, 0, MaximumSpeedLimit))
9121  {
9122  return(false); // SignallerMaxSpeed
9123 
9124  }
9125  if(!Utilities->CheckFileBool(InFile))
9126  {
9127  return(false); // HoldAtLocationInTTMode
9128 
9129  }
9130  if(!Utilities->CheckFileInt(InFile, 0, 5760))
9131  {
9132  return(false); // RepeatNumber (max 96 x 60 at 1 min intervals)
9133 
9134  }
9135  if(!Utilities->CheckFileInt(InFile, 0, 5760))
9136  {
9137  return(false); // IncrementalMinutes (max 96 x 60)
9138 
9139  }
9140  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
9141  {
9142  return(false); // IncrementalDigits
9143 
9144  }
9145  if(!Utilities->CheckFileInt(InFile, 0, 10000000))
9146  {
9147  return(false); // Mass
9148 
9149  }
9150  if(!Utilities->CheckFileInt(InFile, 0, 100000000))
9151  {
9152  return(false);
9153  }
9154  // FrontElementSpeedLimit - changed at v2.1.0 - effectively
9155  // not checked so as to allow for legacy session files, for new session files limit is set when loaded, see above
9156  if(!Utilities->CheckFileInt(InFile, 0, 10000000))
9157  {
9158  return(false); // FrontElementLength
9159 
9160  }
9161  if(!Utilities->CheckFileDouble(InFile))
9162  {
9163  return(false); // EntrySpeed
9164 
9165  }
9166  if(!Utilities->CheckFileDouble(InFile))
9167  {
9168  return(false); // ExitSpeedHalf
9169 
9170  }
9171  if(!Utilities->CheckFileDouble(InFile))
9172  {
9173  return(false); // ExitSpeedFull
9174 
9175  }
9176  if(!Utilities->CheckFileDouble(InFile))
9177  {
9178  return(false); // TimetableMaxRunningSpeed
9179 
9180  }
9181  if(!Utilities->CheckFileDouble(InFile))
9182  {
9183  return(false); // MaxRunningSpeed
9184 
9185  }
9186  if(!Utilities->CheckFileDouble(InFile))
9187  {
9188  return(false); // MaxExitSpeed
9189 
9190  }
9191  if(!Utilities->CheckFileDouble(InFile))
9192  {
9193  return(false); // MaxBrakeRate
9194 
9195  }
9196  if(!Utilities->CheckFileDouble(InFile))
9197  {
9198  return(false); // BrakeRate
9199 
9200  }
9201  if(!Utilities->CheckFileDouble(InFile))
9202  {
9203  return(false); // PowerAtRail
9204 
9205  }
9206  if(!Utilities->CheckFileBool(InFile))
9207  {
9208  return(false); // FirstHalfMove
9209 
9210  }
9211  if(!Utilities->CheckFileBool(InFile))
9212  {
9213  return(false); // OneLengthAccelDecel
9214 
9215  }
9216  if(!Utilities->CheckFileDouble(InFile))
9217  {
9218  return(false); // double(EntryTime)
9219 
9220  }
9221  if(!Utilities->CheckFileDouble(InFile))
9222  {
9223  return(false); // double(ExitTimeHalf)
9224 
9225  }
9226  if(!Utilities->CheckFileDouble(InFile))
9227  {
9228  return(false); // double(ExitTimeFull)
9229 
9230  }
9231  if(!Utilities->CheckFileDouble(InFile))
9232  {
9233  return(false); // double(ReleaseTime)
9234 
9235  }
9236  if(!Utilities->CheckFileDouble(InFile))
9237  {
9238  return(false); // double(TRSTime)
9239 
9240  }
9241  if(!Utilities->CheckFileDouble(InFile))
9242  {
9243  return(false); // double(LastActionTime)
9244 
9245  }
9246  if(!Utilities->CheckFileBool(InFile))
9247  {
9248  return(false); // CallingOnFlag
9249 
9250  }
9251  if(!Utilities->CheckFileBool(InFile))
9252  {
9253  return(false); // BeingCalledOn
9254 
9255  }
9256  if(!Utilities->CheckFileBool(InFile))
9257  {
9258  return(false); // DepartureTimeSet
9259 
9260  }
9261  if(!Utilities->CheckFileInt(InFile, 0, 2))
9262  {
9263  return(false); // (short)TrainMode
9264 
9265  }
9266  if(!Utilities->CheckFileBool(InFile))
9267  {
9268  return(false); // TimetableFinished
9269 
9270  }
9271  if(!Utilities->CheckFileBool(InFile))
9272  {
9273  return(false); // LastActionDelayFlag
9274 
9275  }
9276  if(!Utilities->CheckFileBool(InFile))
9277  {
9278  return(false); // SignallerRemoved
9279 
9280  }
9281  if(!Utilities->CheckFileBool(InFile))
9282  {
9283  return(false); // TerminatedMessageSent
9284 
9285  }
9286  if(!Utilities->CheckFileBool(InFile))
9287  {
9288  return(false); // Derailed
9289 
9290  }
9291  if(!Utilities->CheckFileBool(InFile))
9292  {
9293  return(false); // DerailPending
9294 
9295  }
9296  if(!Utilities->CheckFileBool(InFile))
9297  {
9298  return(false); // Crashed
9299 
9300  }
9301  if(!Utilities->CheckFileBool(InFile))
9302  {
9303  return(false); // StoppedAtBuffers
9304 
9305  }
9306  if(!Utilities->CheckFileBool(InFile))
9307  {
9308  return(false); // StoppedAtSignal
9309 
9310  }
9311  if(!Utilities->CheckFileBool(InFile))
9312  {
9313  return(false); // StoppedAtLocation
9314 
9315  }
9316  if(!Utilities->CheckFileBool(InFile))
9317  {
9318  return(false); // SignallerStopped
9319 
9320  }
9321  if(!Utilities->CheckFileBool(InFile))
9322  {
9323  return(false); // StoppedAfterSPAD
9324 
9325  }
9326  if(!Utilities->CheckFileBool(InFile))
9327  {
9328  return(false); // StoppedForTrainInFront
9329 
9330  }
9331  if(!Utilities->CheckFileBool(InFile))
9332  {
9333  return(false); // NotInService
9334 
9335  }
9336  if(!Utilities->CheckFileBool(InFile))
9337  {
9338  return(false); // Plotted
9339 
9340  }
9341  if(!Utilities->CheckFileBool(InFile))
9342  {
9343  return(false); // TrainGone
9344 
9345  }
9346  if(!Utilities->CheckFileBool(InFile))
9347  {
9348  return(false); // SPADFlag
9349 
9350  }
9351  if(!Utilities->CheckFileBool(InFile))
9352  {
9353  return(false); // TimeTimeLocArrived
9354 
9355  }
9356  if(!Utilities->CheckFileInt(InFile, 0, 15))
9357  {
9358  return(false); // HOffset[0]
9359 
9360  }
9361  if(!Utilities->CheckFileInt(InFile, 0, 15))
9362  {
9363  return(false); // HOffset[1]
9364 
9365  }
9366  if(!Utilities->CheckFileInt(InFile, 0, 15))
9367  {
9368  return(false); // HOffset[2]
9369 
9370  }
9371  if(!Utilities->CheckFileInt(InFile, 0, 15))
9372  {
9373  return(false); // HOffset[3]
9374 
9375  }
9376  if(!Utilities->CheckFileInt(InFile, 0, 15))
9377  {
9378  return(false); // VOffset[0]
9379 
9380  }
9381  if(!Utilities->CheckFileInt(InFile, 0, 15))
9382  {
9383  return(false); // VOffset[1]
9384 
9385  }
9386  if(!Utilities->CheckFileInt(InFile, 0, 15))
9387  {
9388  return(false); // VOffset[2]
9389 
9390  }
9391  if(!Utilities->CheckFileInt(InFile, 0, 15))
9392  {
9393  return(false); // VOffset[3]
9394 
9395  }
9396  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9397  {
9398  return(false); // PlotElement[0]
9399 
9400  }
9401  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9402  {
9403  return(false); // PlotElement[1]
9404 
9405  }
9406  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9407  {
9408  return(false); // PlotElement[2]
9409 
9410  }
9411  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9412  {
9413  return(false); // PlotElement[3]
9414 
9415  }
9416  if(!Utilities->CheckFileInt(InFile, 0, 3))
9417  {
9418  return(false); // PlotEntryPos[0]
9419 
9420  }
9421  if(!Utilities->CheckFileInt(InFile, 0, 3))
9422  {
9423  return(false); // PlotEntryPos[1]
9424 
9425  }
9426  if(!Utilities->CheckFileInt(InFile, 0, 3))
9427  {
9428  return(false); // PlotEntryPos[2]
9429 
9430  }
9431  if(!Utilities->CheckFileInt(InFile, 0, 3))
9432  {
9433  return(false); // PlotEntryPos[3]
9434 
9435  }
9436  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9437  {
9438  return(false); // TrainCrashedInto
9439 
9440  }
9441  if(!Utilities->CheckFileInt(InFile, 0, 2))
9442  {
9443  return(false); // (short)Straddle
9444 
9445  }
9446  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
9447  {
9448  return(false); // NextTrainID
9449 
9450  }
9451  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
9452  {
9453  return(false); // TrainID
9454 
9455  }
9456  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9457  {
9458  return(false); // LeadElement
9459 
9460  }
9461  if(!Utilities->CheckFileInt(InFile, 0, 3))
9462  {
9463  return(false); // LeadEntryPos
9464 
9465  }
9466  if(!Utilities->CheckFileInt(InFile, 0, 3))
9467  {
9468  return(false); // LeadExitPos
9469 
9470  }
9471  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9472  {
9473  return(false); // MidElement
9474 
9475  }
9476  if(!Utilities->CheckFileInt(InFile, 0, 3))
9477  {
9478  return(false); // MidEntryPos
9479 
9480  }
9481  if(!Utilities->CheckFileInt(InFile, 0, 3))
9482  {
9483  return(false); // MidExitPos
9484 
9485  }
9486  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9487  {
9488  return(false); // LagElement
9489 
9490  }
9491  if(!Utilities->CheckFileInt(InFile, 0, 3))
9492  {
9493  return(false); // LagEntryPos
9494 
9495  }
9496  if(!Utilities->CheckFileInt(InFile, 0, 3))
9497  {
9498  return(false); // LagExitPos
9499 
9500  }
9501  if(!Utilities->CheckFileInt(InFile, 0, 14))
9502  {
9503  return(false);
9504  }
9505  // Background colour number //14 is failed colour at v2.4.0
9506  if(!Utilities->CheckFileBool(InFile))
9507  {
9508  return(false); // ForwardHeadCode
9509 
9510  }
9511  if(!Utilities->CheckFileInt(InFile, 0, 10000))
9512  {
9513  return(false); // TrainDataEntryValue
9514 
9515  }
9516  if(!Utilities->CheckFileInt(InFile, 0, 10000))
9517  {
9518  return(false); // ActionVectorEntryValue
9519 
9520  }
9522  {
9523  return(false); // End of train marker + possible RestoreTimetableLocation
9524 
9525  }
9526  // and StoppedWithoutPower flag
9527  return(true);
9528 }
9529 
9530 // ---------------------------------------------------------------------------
9531 
9532 void TTrain::PlotTrainInZoomOutMode(int Caller, bool Flash)
9533 {
9534  // order below reflects significance so earlier shows first, as may have more than one flag set
9535  // only plot flashing trains when Flash is true
9536 
9537 /*
9538  clCrashedBackground (TColor)0x0000FF red
9539  clDerailedBackground (TColor)0x0000FF red
9540  clSPADBackground (TColor)0x00FFFF yellow
9541  clTrainFailedBackground (TColor)0x0066FF orange new at v2.4.0
9542  clCallOnBackground (TColor)0xFF33FF light magenta
9543  clSignalStopBackground (TColor)0x00FF66 green
9544  clBufferAttentionNeeded (TColor)0xFFFF00 cyan
9545  clStationStopBackground (TColor)0xCCFFCC pale green
9546  clTRSBackground (TColor)0xFFCCFF light pink
9547  clBufferStopBackground (TColor)0xFFFFCC pale cyan
9548  clStoppedTrainInFront (TColor)0xFF9999 lavender blue
9549  clSignallerStopped (TColor)0x99CCFF caramel
9550  clNormalBackground (TColor)0xCCCCCC grey
9551 */
9552 
9553  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainInZoomOutMode" + "," + HeadCode);
9554  bool HideFlashingTrain = true;
9555  // hide it when Flash false so it blinks on and off
9556  // if don't hide it it stays displayed all the time
9557  Graphics::TBitmap *SmallTrainBitmap;
9558 
9559  // NB ensure retain same order as zoomed in order so colours correspond
9561  {
9562  TrainController->CrashWarning = true;
9563  SmallTrainBitmap = RailGraphics->smRed;
9564  }
9566  {
9568  SmallTrainBitmap = RailGraphics->smRed;
9569  }
9571  {
9572  TrainController->SPADWarning = true;
9573  SmallTrainBitmap = RailGraphics->smYellow;
9574  }
9576  {
9578  SmallTrainBitmap = RailGraphics->smOrange;
9579  }
9581  {
9583  SmallTrainBitmap = RailGraphics->smMagenta;
9584  }
9586  {
9588  SmallTrainBitmap = RailGraphics->smBrightGreen;
9589  }
9591  {
9593  SmallTrainBitmap = RailGraphics->smCyan;
9594  }
9596  {
9597  SmallTrainBitmap = RailGraphics->smPaleGreen;
9598  HideFlashingTrain = false;
9599  }
9601  {
9602  SmallTrainBitmap = RailGraphics->smCyan;
9603  HideFlashingTrain = false;
9604  }
9606  {
9607  SmallTrainBitmap = RailGraphics->smLightBlue;
9608  HideFlashingTrain = false;
9609  }
9611  {
9612  SmallTrainBitmap = RailGraphics->smCaramel;
9613  HideFlashingTrain = false;
9614  }
9615  else
9616  {
9617  SmallTrainBitmap = RailGraphics->smBlack; // moving
9618  HideFlashingTrain = false;
9619  }
9620  // now plot the new train
9621  // just plot lead & mid, unless lead == -1 in which case plot mid & lag
9622  if((LeadElement > -1) && (!HideFlashingTrain || Flash))
9623  {
9624  Display->PlotSmallOutput(4, Track->TrackElementAt(441, LeadElement).HLoc * 4, Track->TrackElementAt(442, LeadElement).VLoc * 4, SmallTrainBitmap);
9625  }
9626  if((MidElement > -1) && (!HideFlashingTrain || Flash))
9627  {
9628  Display->PlotSmallOutput(5, Track->TrackElementAt(443, MidElement).HLoc * 4, Track->TrackElementAt(444, MidElement).VLoc * 4, SmallTrainBitmap);
9629  }
9630  if((LeadElement == -1) && (LagElement > -1) && (!HideFlashingTrain || Flash))
9631  {
9632  Display->PlotSmallOutput(6, Track->TrackElementAt(445, LagElement).HLoc * 4, Track->TrackElementAt(446, LagElement).VLoc * 4, SmallTrainBitmap);
9633  }
9637  Utilities->CallLogPop(1459);
9638 }
9639 
9640 // ---------------------------------------------------------------------------
9641 
9643 {
9644  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrainInZoomOutMode," + AnsiString(TrainID) + "," + HeadCode);
9645  if(!Display->ZoomOutFlag)
9646  {
9647  Utilities->CallLogPop(1304);
9648  return;
9649  }
9650  for(int y = 0; y < 3; y++)
9651  {
9652  if(OldZoomOutElement[y] > -1)
9653  {
9654  bool FoundFlag = false;
9655  TTrackElement ATElement = Track->TrackElementAt(717, OldZoomOutElement[y]);
9656  TTrackElement IATElement1, IATElement2;
9657  // default elements to begin with
9658  Display->PlotSmallOutput(7, ATElement.HLoc * 4, ATElement.VLoc * 4, RailGraphics->smSolidBgnd); // plot the blank
9659  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap(14, ATElement.HLoc, ATElement.VLoc, FoundFlag);
9660  // Note, have to plot inactives before track because track has to overwrite NamedLocationElements
9661  if(FoundFlag)
9662  {
9663  IATElement1 = Track->InactiveTrackElementAt(87, IMPair.first);
9664  Display->PlotSmallOutput(8, IATElement1.HLoc * 4, IATElement1.VLoc * 4, IATElement1.SmallGraphicPtr);
9665  if(IMPair.first != IMPair.second)
9666  {
9667  IATElement2 = Track->InactiveTrackElementAt(88, IMPair.second);
9668  Display->PlotSmallOutput(9, IATElement2.HLoc * 4, IATElement2.VLoc * 4, IATElement2.SmallGraphicPtr);
9669  }
9670  }
9671  Display->PlotSmallOutput(10, ATElement.HLoc * 4, ATElement.VLoc * 4, ATElement.SmallGraphicPtr);
9672  }
9673  }
9674  Utilities->CallLogPop(1305);
9675 }
9676 
9677 // ---------------------------------------------------------------------------
9678 
9679 bool TTrain::TrainAtLocation(int Caller, AnsiString &LocationName)
9680 {
9681  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainAtLocation" + "," + HeadCode);
9682  LocationName = "";
9683  if(!RevisedStoppedAtLoc())
9684  {
9685  Utilities->CallLogPop(1398);
9686  return(false);
9687  }
9688  if(LeadElement > -1)
9689  {
9691  }
9692  if((LocationName == "") && (MidElement > -1))
9693  {
9694  LocationName = Track->TrackElementAt(682, MidElement).ActiveTrackElementName;
9695  }
9696  if((LocationName == "") && (LagElement > -1))
9697  {
9698  LocationName = Track->TrackElementAt(683, LagElement).ActiveTrackElementName;
9699  }
9700  if(LocationName == "")
9701  {
9702  throw Exception("Error - Location name not set in TrainAtLocation");
9703  }
9704  Utilities->CallLogPop(1399);
9705  return(true);
9706 }
9707 
9708 // ---------------------------------------------------------------------------
9709 
9710 void TTrain::PlotTrain(int Caller, TDisplay *Disp)
9711 {
9712  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrain" + "," + HeadCode);
9713  for(int x = 0; x < 4; x++)
9714  {
9715  PlotTrainGraphic(7, x, Disp);
9716  }
9718  Utilities->CallLogPop(647);
9719 }
9720 
9721 // ---------------------------------------------------------------------------
9722 
9723 void TTrain::WriteTrainToImage(int Caller, Graphics::TBitmap *Bitmap)
9724 {
9725  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WriteTrainToImage" + "," + HeadCode);
9726  for(int x = 0; x < 4; x++)
9727  {
9728  if(PlotElement[x] > -1)
9729  {
9730  Bitmap->Canvas->Draw(((Track->TrackElementAt(744, PlotElement[x]).HLoc - Track->GetHLocMin()) * 16 + HOffset[x]),
9731  ((Track->TrackElementAt(745, PlotElement[x]).VLoc - Track->GetVLocMin()) * 16 + VOffset[x]), HeadCodePosition[x]);
9732  }
9733  }
9735  {
9736  Bitmap->Canvas->Draw(LongServRefTextH - (Track->GetHLocMin() * 16), LongServRefTextV - (Track->GetVLocMin() * 16), ImageLongServRefBitmap);
9737  }
9738  Utilities->CallLogPop(1708);
9739 }
9740 
9741 // ---------------------------------------------------------------------------
9742 
9743 bool TTrain::LinkOccupied(int Caller, int TrackVectorPosition, int LinkNumber) // added at v1.2.0
9744 {
9745  // return true for any part of train occupying LinkNumber at TrackVectorPosition, false for anything else, including no LinkNumber & no TrackVectorPosition
9746  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LinkOccupied," + AnsiString(TrackVectorPosition) + "," +
9747  AnsiString(LinkNumber) + "," + HeadCode);
9748 
9749 /* Note on Straddle: Straddle defines the actual train position wrt Lag, Mid & Lead elements at all times other than within UpdateTrain. Is only MidLag outside UpdateTrain
9750  on first entry at a continuation (with no train plotted), and that has no relevance here. In all other cases it is either LeadMid (when train fully
9751  on Lead & Mid elements) or LeadMidLag (when train straddling 3 elements).
9752 */
9753 
9754  // note that MidElement always fully occupied
9755  if((MidElement == TrackVectorPosition) && ((Track->TrackElementAt(883, TrackVectorPosition).Link[MidEntryPos] == LinkNumber) || (Track->TrackElementAt(884,
9756  TrackVectorPosition).Link[MidExitPos] == LinkNumber)))
9757  {
9758  Utilities->CallLogPop(2005);
9759  return(true);
9760  }
9761  if(Straddle == LeadMid)
9762  {
9763  if((LeadElement == TrackVectorPosition) && ((Track->TrackElementAt(885, TrackVectorPosition).Link[LeadEntryPos] == LinkNumber) ||
9764  (Track->TrackElementAt(886, TrackVectorPosition).Link[LeadExitPos] == LinkNumber)))
9765  {
9766  Utilities->CallLogPop(2006);
9767  return(true);
9768  }
9769  }
9770  else if(Straddle == LeadMidLag)
9771  {
9772  if((LeadElement == TrackVectorPosition) && (Track->TrackElementAt(887, TrackVectorPosition).Link[LeadEntryPos] == LinkNumber))
9773  // only interested in LeadEntryPos as train not occupying ExitPos yet
9774  {
9775  Utilities->CallLogPop(2007);
9776  return(true);
9777  }
9778  else if((LagElement == TrackVectorPosition) && (Track->TrackElementAt(888, TrackVectorPosition).Link[LagExitPos] == LinkNumber))
9779  // only interested in LagExitPos as train has left EntryPos
9780  {
9781  Utilities->CallLogPop(2008);
9782  return(true);
9783  }
9784  }
9785  Utilities->CallLogPop(2009);
9786  return(false);
9787 }
9788 
9789 // ---------------------------------------------------------------------------
9790 
9791 float TTrain::CalcTimeToAct(int Caller, float &TimeToExit, THVShortPair &ExitPair) // only called for running trains.
9796 // TimeToExit & ExitPair added for multiplayer
9797 {
9798  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CalcTimeToAct, " + HeadCode);
9799  int DistanceToRedSignal = 0, DistanceToExit = -1;
9800  float TimeToAct = 0, LastTimeToExit = TimeToExit;
9801  TimeToExit = -1;
9802  ExitPair.first = -1;
9803  ExitPair.second = -1;
9804  float MinsEarly = 0; //added at v2.6.1
9805  TDateTime DepartureTime; //added at v2.6.1 //ArrivalTime used instead of this at v2.9.0 but still calculate it in case need it later for some reason
9806  TDateTime ArrivalTime; //added at v2.9.0 as MinsEarly used DepartureTime which wasn't correct
9807  float TempTTE;
9808 
9809  if(TrainFailed && Stopped() && (TrainMode != Signaller))
9810  {
9811  Utilities->CallLogPop(2147);
9812  return(0); // time to act now, time to exit set above
9813  }
9814  if(SignallerStopped)
9815  {
9816  Utilities->CallLogPop(2080);
9817  return(-1); //time to exit set above
9818  }
9819  if(BeingCalledOn) //added at v2.7.0 so zero time to act cancelled right away
9820  {
9821  Utilities->CallLogPop(2266);
9822  return(-1); // time to exit set above
9823  }
9824 
9825  // check if exiting at a continuation, if so there's no action time needed but still need exit time & ExitPair
9826  if((LeadElement == -1) && (MidElement == -1) && (LagElement > -1) && (Track->TrackElementAt(1411, LagElement).TrackType == Continuation)
9827  /*&& (ExitSpeedFull > 1)*/) //LagElement is the exit //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9828  {
9829  if(Straddle == LeadMidLag) //only half of rear train element on exit, 0.5 lengths to exit
9830  {
9831  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9832  {
9833  TempTTE = (0.5 * Track->TrackElementAt(1412, LagElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9834  if(TempTTE < LastTimeToExit)
9835  {
9836  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9837  }
9838  else
9839  {
9840  TimeToExit = LastTimeToExit;
9841  }
9842  }
9843  else
9844  {
9845  TimeToExit = LastTimeToExit;
9846  }
9847  ExitPair.first = Track->TrackElementAt(1413, LagElement).HLoc;
9848  ExitPair.second = Track->TrackElementAt(1414, LagElement).VLoc;
9849  Utilities->CallLogPop(2342);
9850  return(-1);
9851  }
9852  else
9853  {
9854  TimeToExit = 0; //all train exited
9855  ExitPair.first = Track->TrackElementAt(1415, LagElement).HLoc;
9856  ExitPair.second = Track->TrackElementAt(1416, LagElement).VLoc;
9857  Utilities->CallLogPop(2343);
9858  return(-1);
9859  }
9860  }
9861  if((LeadElement == -1) && (MidElement > -1) && (Track->TrackElementAt(1417, MidElement).TrackType == Continuation)/* && (ExitSpeedFull > 1)*/)
9862  //here MidElement is the exit //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9863  {
9864  if(Straddle == LeadMidLag) //front element of train half off the exit, 1.5 lengths to exit
9865  {
9866  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9867  {
9868  TempTTE = (1.5 * Track->TrackElementAt(1418, MidElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9869  if(TempTTE < LastTimeToExit)
9870  {
9871  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9872  }
9873  else
9874  {
9875  TimeToExit = LastTimeToExit;
9876  }
9877  }
9878  else
9879  {
9880  TimeToExit = LastTimeToExit;
9881  }
9882  ExitPair.first = Track->TrackElementAt(1419, MidElement).HLoc;
9883  ExitPair.second = Track->TrackElementAt(1420, MidElement).VLoc;
9884  Utilities->CallLogPop(2344);
9885  return(-1);
9886  }
9887  else //front element of train fully off the exit, one length to exit
9888  {
9889  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9890  {
9891  TempTTE = (Track->TrackElementAt(1421, MidElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9892  if(TempTTE < LastTimeToExit)
9893  {
9894  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9895  }
9896  else
9897  {
9898  TimeToExit = LastTimeToExit;
9899  }
9900  }
9901  else
9902  {
9903  TimeToExit = LastTimeToExit;
9904  }
9905  ExitPair.first = Track->TrackElementAt(1422, MidElement).HLoc;
9906  ExitPair.second = Track->TrackElementAt(1423, MidElement).VLoc;
9907  Utilities->CallLogPop(2345);
9908  return(-1);
9909  }
9910  }
9911  if(LeadElement > -1)
9912  {
9914  /* && (ExitSpeedFull > 1)*/) //LeadElement is the exit. If LeadMidLag have lead half on exit or if LeadMid have lead full on exit
9915  { //if LeadMidLag have 2.5 lengths to exit, else have 2 lengths to exit
9916  //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9917  if(Straddle == LeadMidLag)
9918  {
9919  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9920  {
9921  TempTTE = (2.5 * Track->TrackElementAt(1426, LeadElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9922  if(TempTTE < LastTimeToExit)
9923  {
9924  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9925  }
9926  else
9927  {
9928  TimeToExit = LastTimeToExit;
9929  }
9930  }
9931  else
9932  {
9933  TimeToExit = LastTimeToExit;
9934  }
9935  ExitPair.first = Track->TrackElementAt(1427, LeadElement).HLoc;
9936  ExitPair.second = Track->TrackElementAt(1428, LeadElement).VLoc;
9937  Utilities->CallLogPop(2346);
9938  return(-1);
9939  }
9940  else
9941  {
9942  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9943  {
9944  TempTTE = (2 * Track->TrackElementAt(1429, LeadElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9945  if(TempTTE < LastTimeToExit)
9946  {
9947  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9948  }
9949  else
9950  {
9951  TimeToExit = LastTimeToExit;
9952  }
9953  }
9954  else
9955  {
9956  TimeToExit = LastTimeToExit;
9957  }
9958  ExitPair.first = Track->TrackElementAt(1430, LeadElement).HLoc;
9959  ExitPair.second = Track->TrackElementAt(1431, LeadElement).VLoc;
9960  Utilities->CallLogPop(2347);
9961  return(-1);
9962  }
9963  }
9964  }
9965 //here if LeadElement > -1 and its forward connection also > -1
9966 
9967  // calc distance to next red signal
9968  if(!Stopped() || RevisedStoppedAtLoc())
9969  {
9970  int FirstPosToBeMeasured = Track->TrackElementAt(953, LeadElement).Conn[LeadExitPos];
9971  int FirstEntryPos = Track->TrackElementAt(954, LeadElement).ConnLinkPos[LeadExitPos];
9972  if((Straddle == LeadMidLag) && (TrainMode == Timetable))
9973 /* In TTMode it's important to set the first element to be measured ahead of the lead element only when the train fully on
9974  2 elements. Otherwise, if the train is only half on the lead element and approaching a station stop where the platform doesn't
9975  extend beyond the lead element stop point, the element ahead of the lead element is not a location whereas the ActionVector
9976  still points to the station stop location. In these circumstances the train hasn't yet stopped, so the dwell time at the
9977  stop isn't calculated, and the station to be stopped at isn't found as a future stop and nor are any other future stops
9978  because the ActionVector name never matches a future station. Hence all dwell times are omitted until the train lands fully
9979  on two elements. To avoid this when Straddle is LeadMidLag the first element to be measured is set to the lead element, so
9980  before the train has stopped the current station is still recognised as a future stop.
9981  In signaller mode stops don't count, and if pass stop signal command is given then when have LeadMidLag the current element
9982  becomes the signal, and the time to act indication becomes 'NOW'.
9983 */
9984  {
9985  FirstPosToBeMeasured = LeadElement;
9986  FirstEntryPos = LeadEntryPos;
9987  }
9988  float CurrentStopTime; // set to 0 at start of function
9989  float LaterStopTime; // set to 0 at start of function
9990  float RecoverableTime; // set to 0 at start of function
9991  int AvTrackSpeed; // set to zero at start of function
9992  bool SigControlAndCanPassRedSignal = ((TrainMode == Signaller) && AllowedToPassRedSignal);
9993  DistanceToRedSignal = TrainController->CalcDistanceToRedSignalandStopTime(0, FirstPosToBeMeasured, FirstEntryPos, SigControlAndCanPassRedSignal,
9994  ActionVectorEntryPtr, HeadCode, TrainID, CurrentStopTime, LaterStopTime, RecoverableTime, AvTrackSpeed, DistanceToExit, ExitPair);
9995 //at this point can't have both DistanceToRedSignal and DistanceToExit both set. Either both will be unset (-1) or one will be set.
9996 //Therefore since need to calculate the time for each in the same way use a generic
9997 
9998  if((DistanceToRedSignal == -1) && (DistanceToExit == -1))// both unset so no action needed
9999  {
10000  TimeToExit = -1;
10001  Utilities->CallLogPop(2076);
10002  return(-1);
10003  }
10004 //else one or other is set
10005  bool DistanceToRedSignalSet = (DistanceToRedSignal > -1);
10006  bool DistanceToExitSet = (DistanceToExit > -1);
10007  int GenericDistance = DistanceToRedSignal;
10008  if(DistanceToExitSet)
10009  {
10010  GenericDistance = DistanceToExit;
10011  }
10012 /* Have MinsDelayed; pos or neg,
10013  CurrentStopTime; pos or zero
10014  LaterStopTime; pos or zero
10015  RecoverableTime; pos or zero
10016 
10017  & from these calculate TotalStopTime. noting that:
10018  If stopped CurrentStopTime automatically adjusts for all early running and for as much late running as possible
10019  RecoverableTime always < LaterStopTime or both zero
10020  can't subtract more than RecoverableTime (MinsDelayed > 0)
10021  only subtract from LaterStopTime, not CurrentTime (MinsDelayed > 0)
10022  only subtract from LaterStopTime if LaterStopTime > 0 (MinsDelayed > 0)
10023  only add to LaterStopTime if LaterStopTime > 0 (MinsDelayed < 0)
10024  if running early & stopped at location CurrentStopTime will automatically include the excess
10025 */
10026  float TimeToSubtract, TotalStopTime;
10027  if(MinsDelayed > RecoverableTime)
10028  {
10029  TimeToSubtract = RecoverableTime;
10030  }
10031  else
10032  {
10033  TimeToSubtract = MinsDelayed; // may be negative;
10034  }
10035  if((AvTrackSpeed > 0) && (DistanceToStationStop <= GenericDistance) && (DistanceToStationStop > 0)) //protection against div by zero, not needed of no stop
10036  //before red signal, DistanceToStationStop != 0 as set to 0 if invalid
10037  //added at v2.6.1, DistanceToStationStop is calculated in SetTrainMovementValues, AvTrackSpeed is average to next red signal, but should be ok to use for
10038  //next station stop
10039  //after 2.7.0 changed (DistanceToStationStop < DistanceToRedSignal) to (DistanceToStationStop <= DistanceToRedSignal) because often have departure signal
10040  //next to the stop platform and if so the two distances are the same and the station stop time isn't included. Also after 2.7.0 used GetRepeatTime... to calc
10041  //departure time because ActionVectorEntryPtr->DepartureTime is the base time and therefore incorrect for repeats.
10042  //first find departure time from the next stop
10043  //at v2.9.0 changed MinsEarly calc to use ArrivalTime instead of departure time
10044  {
10045  if(ActionVectorEntryPtr->FormatType == TimeTimeLoc) //if already arrived then MinsEarly will be < 0 so becomes set to 0
10046  {
10049  MinsEarly = (double(ArrivalTime - TrainController->TTClockTime) * 86400 / 60) - (DistanceToStationStop * 3.6 / 60 / AvTrackSpeed);
10050  }
10051  else if((ActionVectorEntryPtr->FormatType == TimeLoc) && (ActionVectorEntryPtr->ArrivalTime != TDateTime(-1))) // not arrived yet
10052  {
10054  MinsEarly = (double(ArrivalTime - TrainController->TTClockTime) * 86400 / 60) - (DistanceToStationStop * 3.6 / 60 / AvTrackSpeed);
10055  if((ActionVectorEntryPtr + 1)->FormatType == TimeLoc)
10056  {
10057  // must be a departure
10058  DepartureTime = TrainController->GetRepeatTime(70, (ActionVectorEntryPtr + 1)->DepartureTime, RepeatNumber, IncrementalMinutes);
10059  }
10060  }
10061  else if((ActionVectorEntryPtr->FormatType == TimeLoc) && (ActionVectorEntryPtr->ArrivalTime == TDateTime(-1))) //already arrived
10062  {
10063  MinsEarly = 0;
10064  }
10065  if(MinsEarly < 0)
10066  {
10067  MinsEarly = 0;
10068  }
10069  }
10070  if(MinsDelayed < 0) // MinsDelayed < 0 means have arrived early at a station
10071  {
10072  if(CurrentStopTime > 0)
10073  {
10074  TotalStopTime = CurrentStopTime + LaterStopTime;
10075  }
10076  // stopped at loc, will depart on time
10077  else
10078  {
10079  TotalStopTime = LaterStopTime - MinsDelayed;
10080  }
10081  // not stopped, will depart on time at first later stop so add the delay
10082  }
10083  else if((MinsEarly > 0) && !Stopped()) //running early
10084  {
10085  TotalStopTime = LaterStopTime + MinsEarly;
10086  }
10087  else // on time or running late
10088  {
10089  if(LaterStopTime == 0)
10090  {
10091  TotalStopTime = CurrentStopTime;
10092  }
10093  // no later stops, if stopped now will depart as soon as possible,
10094  // if not stopped no stop times to add
10095  else
10096  {
10097  TotalStopTime = CurrentStopTime + LaterStopTime - TimeToSubtract; // later stops so deduct as much as can
10098  }
10099  }
10100  if(AvTrackSpeed < 30)
10101  {
10102  AvTrackSpeed = 30;
10103  }
10104  int Speed = AvTrackSpeed;
10105  if(AvTrackSpeed > int(MaxRunningSpeed))
10106  {
10107  Speed = int(MaxRunningSpeed);
10108  }
10109  if(TrainMode == Signaller)
10110  {
10111  Speed = SignallerMaxSpeed;
10112  TotalStopTime = 0;
10113  }
10114  if(DistanceToRedSignalSet)
10115  {
10116  TimeToAct = TotalStopTime + GenericDistance * 3.6 / 60 / Speed;
10117  // accel & decel taken into account in
10118  // CalcDistanceToRedSignalandStopTime
10119  // 3.6 converts Km/h to m/s & 60 converts seconds to minutes
10120  TimeToExit = -1;
10121  Utilities->CallLogPop(2079);
10122  return(TimeToAct);
10123  }
10124  else //DistanceToExitSet must be true
10125  {
10126  TimeToExit = TotalStopTime + GenericDistance * 3.6 / 60 / Speed;
10127  // accel & decel taken into account in
10128  // CalcDistanceToRedSignalandStopTime
10129  // 3.6 convertsKm/h to m/s & 60 converts seconds to minutes
10130  Utilities->CallLogPop(2370);
10131  return(-1); //no red signal so no time to act
10132  }
10133  }
10134  else // stopped not at location
10135  {
10137  {
10138  TimeToAct = 0.0;
10139  TimeToExit = -1;
10140  }
10141  if(StoppedWithoutPower) //added at v2.13.2 as this situation was missed & time to act was 0 [If train failed then covered above]
10142  {
10143  TimeToAct = -1;
10144  TimeToExit = -1;
10145  }
10146  // but if stopped at a signal & autosigs route after it then ok, provided signal not failed
10147  if(StoppedAtSignal)
10148  {
10149  int NextElement = Track->TrackElementAt(928, LeadElement).Conn[LeadExitPos];
10150  int NextEntryPos = Track->TrackElementAt(929, LeadElement).ConnLinkPos[LeadExitPos];
10151  bool NextElementFailed = Track->TrackElementAt(1548, NextElement).Failed; //added at v2.13.2
10152  int NextExitPos;
10153  if(Track->TrackElementAt(930, NextElement).TrackType == Points)
10154  {
10155  if((NextEntryPos == 0) || (NextEntryPos == 2))
10156  // leading entry point
10157  {
10158  if(Track->TrackElementAt(931, NextElement).Attribute == 0)
10159  {
10160  NextExitPos = 1;
10161  }
10162  else
10163  {
10164  NextExitPos = 3;
10165  }
10166  }
10167  else
10168  {
10169  NextExitPos = 0; // trailing entry point
10170  }
10171  }
10172  else
10173  {
10174  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
10175  }
10176  int NextButOneElement = Track->TrackElementAt(932, NextElement).Conn[NextExitPos];
10177  int NextButOneEntryPos = Track->TrackElementAt(933, NextElement).ConnLinkPos[NextExitPos];
10178  int RouteNumber; // holder for referenced value, not used
10179  if((AllRoutes->GetRouteTypeAndNumber(32, NextButOneElement, NextButOneEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !NextElementFailed)
10180  { //NextElementFailed added at v2.13.2
10181  TimeToAct = -1;
10182  TimeToExit = -1;
10183  }
10184  }
10185  Utilities->CallLogPop(2074);
10186  return(TimeToAct);
10187  }
10188 }
10189 
10190 // ---------------------------------------------------------------------------
10191 
10193 {
10194  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainOnContinuation, " + HeadCode);
10195  if(LeadElement > -1)
10196  {
10198  {
10199  Utilities->CallLogPop(2148);
10200  return(true);
10201  }
10202  }
10203  if(MidElement > -1)
10204  {
10206  {
10207  Utilities->CallLogPop(2149);
10208  return(true);
10209  }
10210  }
10211  if(LagElement > -1)
10212  {
10214  {
10215  Utilities->CallLogPop(2150);
10216  return(true);
10217  }
10218  }
10219  Utilities->CallLogPop(2151);
10220  return(false);
10221 }
10222 
10223 // ---------------------------------------------------------------------------
10224 
10225 // ---------------------------------------------------------------------------
10226 // TTrainController
10227 // ---------------------------------------------------------------------------
10228 
10230 { //this called from ClearEverything() so called whenever load railway or load session, so whenever background changed it is called
10231  OnTimeArrivals = 0;
10232  LateArrivals = 0;
10233  EarlyArrivals = 0;
10234  OnTimePasses = 0;
10235  LatePasses = 0;
10236  EarlyPasses = 0;
10237  OnTimeExits = 0; //these 3 exits added at v2.9.2 - missed in error earlier
10238  LateExits = 0;
10239  EarlyExits = 0;
10240  OnTimeDeps = 0;
10241  LateDeps = 0;
10242  MissedStops = 0;
10243  OtherMissedEvents = 0;
10244  UnexpectedExits = 0;
10245  NumFailures = 0;
10247  AvHoursIntValue = 0;
10248  MTBFHours = 0;
10250  IncorrectExits = 0;
10251  SPADEvents = 0;
10252  SPADRisks = 0;
10253  CrashedTrains = 0;
10254  Derailments = 0;
10255  TotArrDepPass = 0;
10256  TotLateArrMins = 0;
10257  TotEarlyArrMins = 0;
10258  TotLatePassMins = 0;
10259  TotEarlyPassMins = 0;
10260  TotLateExitMins = 0; //added at v2.9.1
10261  TotEarlyExitMins = 0; //added at v2.9.1
10262  TotLateDepMins = 0;
10263  ExcessLCDownMins = 0;
10264  SkippedTTEvents = 0; //added at v2.11.0
10265  TTClockTime = 0; // added for v0.6
10267  // added at v1.3.0 to ensure false at start
10268  OpTimeToActUpdateCounter = 0; // new v2.2.0
10269  OpActionPanelVisible = false; // new v2.2.0
10270  // reset all message flags, stops them being given twice (shouldn't be needed here but add for safety) //new at v2.4.0
10271  SSHigh = false;
10272 // MRSHigh = false; removed at v2.21.0
10273 // MRSLow = false;
10274  MassHigh = false;
10275  BFHigh = false;
10276  BFLow = false;
10277  PwrHigh = false;
10278  SigSHigh = false;
10279  SigSLow = false;
10280  randomize();
10281  // to seed rand() & random() with a random number (see UpdateTrain)
10282  LongServRefFont = new TFont;
10283  LongServRefFont->Name = "Arial";
10284  LongServRefFont->Style = TFontStyles() << fsBold;
10285  LongServRefFont->Size = 8;
10286  if(Utilities->clTransparent == clB5G5R5) //white
10287  {
10288  LongServRefFont->Color = clB1G0R0; //very dark blue
10289  LongServRefFontColNumber = 0x01; //clB1G0R0
10290  NearTransparentColNumber = 0xda; // =218 decimal, was 0xd6; //clB4G5R5 - see RailGraphics->SetWebSafePalette(...
10291  BgndColNumber = 0xd7; //clB5G5R5
10292  }
10293  else if(Utilities->clTransparent == clB0G0R0) //black
10294  {
10295  LongServRefFont->Color = clB3G5R5; //cream //number 0xd5
10296  LongServRefFontColNumber = 0xd5; //clB3G5R5
10297  NearTransparentColNumber = 0xd8; // = 216 decimal, was 0x01; //clB1G0R0 - see RailGraphics->SetWebSafePalette(...
10298  BgndColNumber = 0x00; //clB0G0R0
10299  }
10300  else //clB1G0R0) dark blue
10301  {
10302  LongServRefFont->Color = clB3G5R5; //cream
10303  LongServRefFontColNumber = 0xd5; //clB3G5R5
10304  NearTransparentColNumber = 0xd9; // =217 decimal,was 0x00; //clB0G0R0 - see RailGraphics->SetWebSafePalette(...
10305  BgndColNumber = 0x01; //clB1G0R0
10306  }
10307 }
10308 
10309 // ---------------------------------------------------------------------------
10310 
10312 {
10313  for(unsigned int x = 0; x < TrainVector.size(); x++)
10314  {
10315  TrainVectorAt(32, x).DeleteTrain(4);
10316  }
10317  TrainVector.clear();
10318  delete LongServRefFont;
10319 }
10320 
10321 // ---------------------------------------------------------------------------
10322 
10323 void TTrainController::LogEvent(AnsiString Str)
10324 {
10325  AnsiString FullStr = Utilities->TimeStamp() + "," + TTClockTime.FormatString("hh:nn:ss") + "," + Str;
10326 
10327  // restrict to last 1000 entries
10328  Utilities->EventLog.push_back(FullStr);
10329  if(Utilities->EventLog.size() > 1000)
10330  {
10331  Utilities->EventLog.pop_front();
10332  }
10333 }
10334 
10335 // ---------------------------------------------------------------------------
10336 
10338 {
10339  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",Operate");
10340  bool ClockState = Utilities->Clock2Stopped;
10341  Utilities->Clock2Stopped = true;
10342  // new section dealing with Snt & Snt-sh additions
10343  // BUT don't add trains if points or route flashing [conditions added for Version 0.6 as a result of Najamuddin's error - 15/01/11] - wait until next
10344  // clock tick after stops flashing
10346  {
10347  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
10348  {
10349  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
10350  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
10351  TActionEventType EventType = NoEvent;
10352  if(AVEntry0.Command == "Snt")
10353  {
10354  // calc below only for Snt & Snt-sh entries rather than all entries to save time
10355  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
10356  int IncrementalMinutes = 0;
10357  int IncrementalDigits = 0;
10358  if(AVEntryLast.FormatType == Repeat)
10359  {
10360  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
10361  IncrementalDigits = AVEntryLast.FrontStartOrRepeatDigits;
10362  }
10363  if((AVEntryLast.FormatType == Repeat) && (TDEntry.NumberOfTrains < 2))
10364  {
10365  throw Exception("Error - Repeat entry && less than two trains for Snt entry: " + TDEntry.HeadCode);
10366  }
10367  // see above note
10368 
10369  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
10370  {
10371  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(y);
10372  if(TTOD.RunningEntry != NotStarted)
10373  {
10374  continue;
10375  }
10376 
10377 //Multiplayer: here check for a train entering at a coupling {RearStartOrRepeatMins shows if it's a coupling or not), and can only be a Snt entry
10378 //if so and no arrival signalled yet bypass the timetabled arrival
10379 //if so and arrival signalled then start the new service, using the repeat number and headcode for the entering train
10380 //if a repeat is skipped then should be ok if it arrives later as its RunningEntry is still NotStarted
10381 
10382  if(GetRepeatTime(2, AVEntry0.EventTime, y, IncrementalMinutes) > TTClockTime)
10383  {
10384  break; // all the rest will also be greater
10385  }
10386  AnsiString TrainHeadCode = GetRepeatHeadCode(22, TDEntry.HeadCode, y, IncrementalDigits);
10387  if(AddTrain(2, AVEntry0.RearStartOrRepeatMins, AVEntry0.FrontStartOrRepeatDigits, TrainHeadCode, TDEntry.StartSpeed, TDEntry.Mass,
10388  TDEntry.MaxRunningSpeed, TDEntry.MaxBrakeRate, TDEntry.PowerAtRail, "Timetable", &TDEntry, y, IncrementalMinutes, IncrementalDigits,
10389  TDEntry.SignallerSpeed, AVEntry0.SignallerControl, EventType))
10390  {
10391  TTOD.TrainID = TrainVector.back().TrainID;
10392  TTOD.RunningEntry = Running;
10393  TrainVector.back().Description = TDEntry.FixedDescription; //added at v2.16.1
10394  }
10395  else if(EventType == FailTrainEntry)
10396  {
10397  break; // if a train can't enter no point checking any more repeats as they won't be able to enter either
10398  }
10399  }
10400  }
10401  if(AVEntry0.Command == "Snt-sh")
10402  // just start this once, shuttle repeats take care of restarts
10403  {
10404  // calc below only for Snt & Snt-sh entries rather than all entries to save time
10405  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
10406  int IncrementalMinutes = 0;
10407  int IncrementalDigits = 0;
10408  if(AVEntryLast.FormatType == Repeat)
10409  {
10410  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
10411  IncrementalDigits = AVEntryLast.FrontStartOrRepeatDigits;
10412  }
10413  if((AVEntryLast.FormatType == Repeat) && (TDEntry.NumberOfTrains < 2))
10414  {
10415  throw Exception("Error - Repeat entry && less than two trains for Snt-sh entry: " + TDEntry.HeadCode);
10416  }
10417  // see above note
10418  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(0);
10419  if(TTOD.RunningEntry == NotStarted)
10420  {
10421  if(AVEntry0.EventTime <= TTClockTime)
10422  {
10423  if(AddTrain(3, AVEntry0.RearStartOrRepeatMins, AVEntry0.FrontStartOrRepeatDigits, TDEntry.HeadCode, TDEntry.StartSpeed, TDEntry.Mass,
10424  TDEntry.MaxRunningSpeed, TDEntry.MaxBrakeRate, TDEntry.PowerAtRail, "Timetable", &TDEntry, 0, IncrementalMinutes, IncrementalDigits,
10425  TDEntry.SignallerSpeed, false, EventType))
10426  // false for SignallerControl
10427  {
10428  TTOD.TrainID = TrainVector.back().TrainID;
10429  TTOD.RunningEntry = Running;
10430  TrainVector.back().Description = TDEntry.FixedDescription; //added at v2.16.1
10431  }
10432  else if(EventType == FailTrainEntry)
10433  {
10434  break; // if a train can't enter no point checking any more repeats as they won't be able to enter either
10435  }
10436  }
10437  }
10438  }
10439  }
10440  }
10441 
10442  // deal with running trains but abort if any vectors added, would probably be OK but don't risk a vector reallocation disrupting the
10443  // iteration, next cycle will catch up with any other pending updates
10444  if(!TrainVector.empty())
10445  {
10446  TrainAdded = false;
10447 
10448 //elapsed time investigations
10449 
10450 //elapsed time segment
10451 //double Start, End;
10452 //AnsiString ElapsedTimeReport = "";
10453 //end elasped time segment
10454  AllRoutes->CallonVector.clear(); // this will be rebuilt during the calls to UpdateTrain
10455 //elapsed time segment
10456 //PerfLogForm->PerformanceLog(-1, "\n Train vector size: " + AnsiString(TrainVector.size()) + '\n');
10457 //PerfLogForm->PerformanceLog(-1, "Start time list");
10458 //end elapsed time segment
10459  for(unsigned int x = 0; x < TrainVector.size(); x++)
10460  {
10461 //elapsed time segment
10462 //Start = double(GetTime()) * 86400; //secs
10463 //end elapsed time segment
10464  TrainVectorAt(33, x).UpdateTrain(0);
10465 //elapsed time segment
10466 //End = double(GetTime()) * 86400;
10467 //ElapsedTimeReport = TrainVectorAt(-1, x).TrainDataEntryPtr->ServiceReference + AnsiString(" ") + AnsiString(int((End - Start) * 1000)); //msecs
10468 //PerfLogForm->PerformanceLog(-1, ElapsedTimeReport);
10469 //end elapsed time segment
10470 
10471 //end elapsed time investigations
10472 
10473  /* added HasTrainGone() condition below in v0.4c to prevent 2 trains both having TrainGone set in UpdateTrain
10474  at the same time. That caused the error Craig Weekes reported in November 2010 where 2 trains exited at the same time, and later the TrainVector
10475  iterates in reverse to erase the second train to have gone (when the first train to have gone comes before the second in TrainVector),
10476  but afterwards ReplotTrains iterates forwards and therefore replots the first train to have gone and therefore sets the TrainIDOnElement value
10477  to the exited train, with nothing to reset it. Hovering the mouse over that element with train information enabled causes an error because
10478  the track element thinks the train is still there, whereas it is missing from the TrainVector. BUT subsequently (in v2.11.1) changed RePlotTrains
10479  so it doesn't plot trains with TrainGone set, but left this is as does no harm
10480 
10481  Had another error notified by Kevin Smith on 02/01/22 where a train was manually removed in the same clock cycle as a train exited, and this caused
10482  the same error as above. Did a lot of experimenting but eventually cured it with two changes, first as above in RePlotTrains, and also below adding
10483  a break; command after one TrainHasGone() dealt with. There were introduced in v2.11.1 & seems ok now
10484 
10485  These changes should deal with any number of TrainGone flags set in the same clock cycle - from exiting, manual removal, or joins
10486  */
10487  if(TrainAdded || TrainVectorAt(35, x).HasTrainGone())
10488  {
10489  break; //only one exited train will be dealt with at a time (see below) so no point looking further
10490  }
10491  }
10492  // set warning flags (ManualLCDownAttentionWarning dealt with in InterfaceUnit)
10493  CrashWarning = false;
10494  DerailWarning = false;
10495  SPADWarning = false;
10496  CallOnWarning = false;
10497  SignalStopWarning = false;
10498  BufferAttentionWarning = false;
10499  TrainFailedWarning = false;
10500  for(int x = TrainVector.size() - 1; x >= 0; x--) // reverse because of erase
10501  {
10502  TTrain &Train = TrainVectorAt(34, x);
10503  if(Train.Crashed)
10504  // can't use background colours for crashed & derailed because same colour
10505  {
10506  CrashWarning = true;
10507  }
10508  else if(Train.Derailed)
10509  // can't use background colours for crashed & derailed because same colour
10510  {
10511  DerailWarning = true;
10512  }
10513  else if(Train.BackgroundColour == clSPADBackground)
10514  // use colour as that changes as soon as passes signal
10515  {
10516  SPADWarning = true;
10517  }
10518  else if(Train.BackgroundColour == clTrainFailedBackground)
10519  {
10520  TrainFailedWarning = true;
10521  }
10522  else if(Train.BackgroundColour == clCallOnBackground)
10523  // use colour as also stopped at signal
10524  {
10525  CallOnWarning = true;
10526  }
10527  else if(Train.BackgroundColour == clSignalStopBackground)
10528  // use colour to distinguish from call-on
10529  {
10530  SignalStopWarning = true;
10531  }
10532  else if(Train.BackgroundColour == clBufferAttentionNeeded)
10533  // use colour to distinguish from ordinary buffer stop
10534  {
10535  BufferAttentionWarning = true;
10536  }
10537  if(Train.HasTrainGone())
10538  {
10539  AnsiString Loc = "";
10540  bool ElementFound = false;
10541  TTrackElement TE;
10542  if(Train.LagElement > -1)
10543  {
10544  TE = Track->TrackElementAt(531, Train.LagElement);
10545  ElementFound = true;
10546  }
10547  else if(Train.MidElement > -1)
10548  {
10549  TE = Track->TrackElementAt(779, Train.MidElement);
10550  ElementFound = true;
10551  }
10552  else if(Train.LeadElement > -1)
10553  {
10554  TE = Track->TrackElementAt(780, Train.LeadElement);
10555  ElementFound = true;
10556  }
10557  if(ElementFound)
10558  {
10559  if(TE.ActiveTrackElementName != "")
10560  {
10561  Loc = TE.ActiveTrackElementName + ", track element " + TE.ElementID;
10562  }
10563  else
10564  {
10565  Loc = "track element " + TE.ElementID;
10566  }
10567  }
10568  TActionVectorEntry *AVEntryPtr = Train.ActionVectorEntryPtr;
10569  if((Train.SignallerRemoved) || (Train.JoinedOtherTrainFlag))
10570  // need above first because may also have ActionVectorEntryPtr == "Fer"
10571  {
10572  Train.UnplotTrain(9);
10573  // added at v1.3.0 to reset signals after train removed from an autosigsroute
10575  {
10578  }
10579  // end of addition
10580  AllRoutes->RebuildRailwayFlag = true;
10581  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot LCs
10582  // correctly after a crash
10583  }
10584  else if(AVEntryPtr->Command == "Fer")
10585  {
10586  bool CorrectExit = false;
10587  if(!AVEntryPtr->ExitList.empty())
10588  {
10589  for(TNumListIterator ELIT = AVEntryPtr->ExitList.begin(); ELIT != AVEntryPtr->ExitList.end(); ELIT++)
10590  {
10591  if(*ELIT == Train.LagElement)
10592  {
10593  CorrectExit = true;
10594  }
10595  }
10596  }
10597  if(CorrectExit)
10598  {
10599  Train.LogAction(19, Train.HeadCode, "", Leave, Loc, "", AVEntryPtr->EventTime, AVEntryPtr->Warning);
10600  }
10601  else
10602  {
10603  LogActionError(38, Train.HeadCode, "", FailIncorrectExit, Loc);
10604  }
10605  }
10606  else
10607  {
10608  if(!AVEntryPtr->SignallerControl)
10609  {
10610  LogActionError(26, Train.HeadCode, "", FailUnexpectedExitRailway, Loc);
10611  Train.SendMissedActionLogs(2, -2, AVEntryPtr);
10612  // -2 is marker for send messages for all remaining actions except Fer if present
10613  }
10614  else
10615  {
10616  Train.LogAction(31, Train.HeadCode, "", SignallerLeave, Loc, "", TDateTime(0), false); // false for Warning
10617  }
10618  }
10619  Utilities->CumulativeDelayedRandMinsAllTrains += Train.CumulativeDelayedRandMinsOneTrain; //added at v2.13.0 for random delays
10620  Train.TrainDataEntryPtr->TrainOperatingDataVector.at(Train.RepeatNumber).RunningEntry = Exited;
10621  Train.DeleteTrain(1);
10622  TrainVector.erase(TrainVector.begin() + x);
10623  ReplotTrains(1, Display); //to reset ElementIDs for remaining trains when have removed a train
10624  //NB: won't plot any trains with TrainGone flag set (changed at v2.11.1)
10625  break; //added at v2.11.1 to ensure that only one train with TrainGone set is dealt with in one clock cycle
10626  }
10627  }
10628  }
10629  else
10630  {
10631  // reset all flags in case last train removed with flag set
10632  CrashWarning = false;
10633  DerailWarning = false;
10634  SPADWarning = false;
10635  CallOnWarning = false;
10636  SignalStopWarning = false;
10637  BufferAttentionWarning = false;
10638  TrainFailedWarning = false;
10639  }
10640  // update OpTimeToActMultimap
10642  {
10644  // clears entries then adds values for running trains then for continuation entries
10646  //added for multiplayer for running trains only
10647  }
10648  Utilities->Clock2Stopped = ClockState;
10649  Utilities->CallLogPop(723);
10650 }
10651 
10652 // ---------------------------------------------------------------------------
10654 {
10655  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FinishedOperation");
10656  if(!TrainVector.empty())
10657  {
10658  for(int x = TrainVector.size() - 1; x >= 0; x--)
10659  {
10660  TrainVectorAt(50, x).DeleteTrain(2);
10661  }
10662  TrainVector.clear();
10663  }
10664  if(!TrainDataVector.empty())
10665  {
10666  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
10667  {
10668  TTrainDataEntry &TDEntry = TrainDataVector.at(x);
10669  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
10670  {
10671  TTrainOperatingData &TOD = TDEntry.TrainOperatingDataVector.at(y);
10672  TOD.RunningEntry = NotStarted;
10673  TOD.TrainID = -1;
10674  TOD.EventReported = NoEvent;
10675  }
10676  }
10677  }
10678  Display->GetOutputLog1()->Caption = "";
10679  Display->GetOutputLog2()->Caption = "";
10680  Display->GetOutputLog3()->Caption = "";
10681  Display->GetOutputLog4()->Caption = "";
10682  Display->GetOutputLog5()->Caption = "";
10683  Display->GetOutputLog6()->Caption = "";
10684  Display->GetOutputLog7()->Caption = "";
10685  Display->GetOutputLog8()->Caption = "";
10686  Display->GetOutputLog9()->Caption = "";
10687  Display->GetOutputLog10()->Caption = "";
10688  Utilities->CallLogPop(1352);
10689 }
10690 
10691 // ---------------------------------------------------------------------------
10692 
10694 {
10695  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ReplotTrains");
10696  if(!TrainVector.empty())
10697  {
10698  for(unsigned int x = 0; x < TrainVector.size(); x++)
10699  {
10700  if(!TrainVectorAt(84, x).HasTrainGone()) //added at v2.11.0 to prevent plotting a train pending removal & particularly to prevent TrainElementID's being reinstated
10701  { //see Kevin Smith error information for details
10702  TrainVectorAt(51, x).PlotTrain(4, Disp);
10703  }
10704  }
10705  }
10706  Utilities->CallLogPop(724);
10707 }
10708 
10709 // ---------------------------------------------------------------------------
10710 
10711 void TTrainController::WriteTrainsToImage(int Caller, Graphics::TBitmap *Bitmap)
10712 {
10713  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WriteTrainsToImage");
10714  if(!TrainVector.empty())
10715  {
10716  for(unsigned int x = 0; x < TrainVector.size(); x++)
10717  {
10718  TrainVectorAt(61, x).WriteTrainToImage(0, Bitmap);
10719  }
10720  }
10721  Utilities->CallLogPop(1707);
10722 }
10723 
10724 // ---------------------------------------------------------------------------
10725 
10727 {
10728  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrains");
10729  if(!TrainVector.empty())
10730  {
10731  for(unsigned int x = 0; x < TrainVector.size(); x++)
10732  {
10733  TrainVectorAt(52, x).UnplotTrain(10);
10734  }
10735  }
10737  Utilities->CallLogPop(725);
10738 }
10739 
10740 // ---------------------------------------------------------------------------
10741 
10742 bool TTrainController::AddTrain(int Caller, int RearPosition, int FrontPosition, AnsiString HeadCode, int StartSpeed, int Mass, double MaxRunningSpeed,
10743  double MaxBrakeRate, double PowerAtRail, AnsiString ModeStr, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes,
10744  int IncrementalDigits, int SignallerSpeed, bool SignallerControl, TActionEventType &EventType)
10745 {
10746  LogEvent(AnsiString(Caller) + ",AddTrain," + AnsiString(RearPosition) + "," + AnsiString(FrontPosition) + "," + HeadCode + "," + AnsiString(StartSpeed) +
10747  "," + AnsiString(Mass) + "," + ModeStr);
10748  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AddTrain," + AnsiString(RearPosition) + "," + AnsiString(FrontPosition) +
10749  "," + HeadCode + "," + AnsiString(StartSpeed) + "," + AnsiString(Mass) + "," + ModeStr); //at v2.11.1 dropped later headcode - was listed twice
10750 
10751  int RearExitPos = -1;
10752 
10753  for(int x = 0; x < 4; x++)
10754  {
10755  if(Track->TrackElementAt(519, RearPosition).Conn[x] == FrontPosition)
10756  {
10757  RearExitPos = x;
10758  }
10759  }
10760  if(RearExitPos == -1)
10761  {
10762  throw Exception("Error, RearExit == -1 in AddTrain");
10763  }
10764  bool ReportFlag = true;
10765 
10766  // used to stop repeated messages from CheckStartAllowable when split failed
10767  if(TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported != NoEvent)
10768  {
10769  ReportFlag = false;
10770  }
10771  if(!CheckStartAllowable(0, RearPosition, RearExitPos, HeadCode, ReportFlag, EventType))
10772  {
10773  // messages sent to performance log in CheckStartAllowable if ReportFlag true
10774  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = EventType;
10775  Utilities->CallLogPop(938);
10776  return(false);
10777  }
10778  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = NoEvent;
10779  TTrainMode TrainMode = NoMode;
10780 
10781  if(ModeStr == "Timetable")
10782  {
10783  TrainMode = Timetable;
10784  }
10785  // all else gives 'None', 'Signaller' set within program
10786 
10787  if(MaxRunningSpeed < 10)
10788  {
10789  MaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
10790  }
10791  if(SignallerSpeed < 10)
10792  {
10793  SignallerSpeed = 10; // added at v0.6 to avoid low max speeds
10794  }
10795  TTrain *NewTrain = new TTrain(0, RearPosition, RearExitPos, HeadCode, StartSpeed, Mass, MaxRunningSpeed, MaxBrakeRate, PowerAtRail, TrainMode,
10796  TrainDataEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerSpeed);
10797 
10798  LogEvent("AddTrainSupplemental: Service Ref = " + TrainDataEntryPtr->ServiceReference + ", TrainID = " + AnsiString(NewTrain->TrainID)); //new at v2.11.1 so can relate headcode to ID
10799 
10800  NewTrain->ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0));
10801  // initialise here rather than in TTrain constructor as create trains
10802  // with Null TrainDataEntryPtr when loading session trains
10803  if(SignallerControl)
10804  {
10805  NewTrain->TimetableFinished = true;
10806  NewTrain->SignallerStoppingFlag = false;
10807  NewTrain->TrainMode = Signaller;
10808  if(NewTrain->MaxRunningSpeed > NewTrain->SignallerMaxSpeed)
10809  {
10810  NewTrain->MaxRunningSpeed = NewTrain->SignallerMaxSpeed;
10811  }
10813  }
10814  // deal with starting conditions:-
10815  // unlocated Snt: just report entry & advance pointer
10816  // located Snt or Sfs: set station conditions as would if had reached stop point in Update(), & advance the ActionVectorEntryPtr
10817  // Sns doesn't need a new train
10818  if(NewTrain->ActionVectorEntryPtr->LocationName != "")
10819  // covers all above located starts
10820  // if location of Snt was a station (that is set as LocationName, i.e. not just any station) that isn't next departure station then
10821  // wouldn't have accepted the timetable
10822  {
10823  // first check if LeadElement (can't access LeadElement directly yet as not set, use FrontPosition instead) is buffers, note that
10824  // StoppedAtBuffers is set in UpdateTrain()
10825  if(Track->TrackElementAt(520, FrontPosition).TrackType == Buffers)
10826  // buffer end must be ahead of train or would have failed start position check
10827  {
10828  NewTrain->StoppedAtLocation = true;
10829  NewTrain->PlotStartPosition(0);
10831  NewTrain->LogAction(20, NewTrain->HeadCode, "", Create, NewTrain->ActionVectorEntryPtr->LocationName, "", NewTrain->ActionVectorEntryPtr->EventTime,
10832  NewTrain->ActionVectorEntryPtr->Warning);
10833  if(!SignallerControl) // don't advance if SignalControlEntry
10834  {
10835  NewTrain->ActionVectorEntryPtr++;
10836  // should be a command, could be a location departure but if so can't depart so set 'Hold' anyway
10837  }
10838  NewTrain->LastActionTime = TTClockTime;
10839  }
10840  // else a through station stop
10841  else
10842  {
10843  NewTrain->StoppedAtLocation = true;
10844  NewTrain->PlotStartPosition(10);
10846  NewTrain->LogAction(21, NewTrain->HeadCode, "", Create, NewTrain->ActionVectorEntryPtr->LocationName, "", NewTrain->ActionVectorEntryPtr->EventTime,
10847  NewTrain->ActionVectorEntryPtr->Warning);
10848  if(!SignallerControl) // don't advance if SignalControlEntry
10849  {
10850  NewTrain->ActionVectorEntryPtr++;
10851  }
10852  NewTrain->LastActionTime = TTClockTime;
10853  }
10854  }
10855  else // unlocated entry (i.e. not a stop entry, but could still be at a named location)
10856  {
10857  NewTrain->PlotStartPosition(11);
10858  TTrackElement TE = Track->TrackElementAt(530, NewTrain->RearStartElement);
10859  AnsiString Loc = "";
10860  if(TE.ActiveTrackElementName != "")
10861  {
10862  Loc = TE.ActiveTrackElementName + ", track element " + TE.ElementID;
10863  }
10864  else
10865  {
10866  Loc = "track element " + TE.ElementID;
10867  }
10868  if(TE.TrackType == Continuation)
10869  {
10870  NewTrain->LogAction(22, NewTrain->HeadCode, "", Enter, Loc, "", NewTrain->ActionVectorEntryPtr->EventTime, NewTrain->ActionVectorEntryPtr->Warning);
10871  }
10872  else
10873  {
10874  NewTrain->LogAction(23, NewTrain->HeadCode, "", Create, Loc, "", NewTrain->ActionVectorEntryPtr->EventTime, NewTrain->ActionVectorEntryPtr->Warning);
10875  }
10876  if(!SignallerControl) // don't advance if SignalControlEntry
10877  {
10878  NewTrain->ActionVectorEntryPtr++;
10879  }
10880  NewTrain->LastActionTime = TTClockTime;
10881  // no need to set LastActionTime for an unlocated entry
10882  }
10883  // cancel a wrong-direction route if either element of train starts on one
10884  if(NewTrain->LeadElement > -1)
10885  {
10886  NewTrain->CheckAndCancelRouteForWrongEndEntry(3, NewTrain->LeadElement, NewTrain->LeadEntryPos);
10887  }
10888  if(NewTrain->MidElement > -1)
10889  {
10890  NewTrain->CheckAndCancelRouteForWrongEndEntry(4, NewTrain->MidElement, NewTrain->MidEntryPos);
10891  }
10892  // set signals for a right-direction autosigs route for either element of train on one
10893  // erase elements back to start for a non-autosigs route & check if an autosigs route immediately behind it, and if so set its signals
10894  // note that all but autosigs routes become part of a single route, so there can only be an autosigs route behind the non-autosigs route
10895  int RouteNumber = -1;
10896  bool SignalsSet = false;
10897 
10898  if(NewTrain->LeadElement > -1)
10899  {
10900  if(AllRoutes->GetRouteTypeAndNumber(13, NewTrain->LeadElement, NewTrain->LeadEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute)
10901  {
10902  // below added in place of SetRouteSignals in v2.4.0 as don't want to set signals from start of route for a new train addition
10903  int RouteStartPosition;
10904  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
10905  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(21, Track->TrackElementAt(955, FrontPosition).HLoc,
10906  Track->TrackElementAt(956, FrontPosition).VLoc, SecondPair);
10907  if(FirstPair.first == RouteNumber)
10908  {
10909  RouteStartPosition = FirstPair.second;
10910  }
10911  else if(SecondPair.first == RouteNumber)
10912  {
10913  RouteStartPosition = SecondPair.second;
10914  }
10915  else
10916  {
10917  throw Exception("Error, RouteNumber not found in Route2MultiMap in 1st of 2 calls to SetAllRearwardsSignals in AddTrain");
10918  }
10919  AllRoutes->SetAllRearwardsSignals(10, 0, RouteNumber, RouteStartPosition);
10920  SignalsSet = true;
10921  // AllRoutes->GetFixedRouteAt(, RouteNumber).SetRouteSignals(); above substituted in v2.4.0
10922  }
10923  else if(RouteNumber > -1) // non-autosigsroute
10924  {
10925  TPrefDirElement TempPDE = AllRoutes->GetFixedRouteAt(181, RouteNumber).GetFixedPrefDirElementAt(194, 0);
10926  int FirstTVPos = TempPDE.GetTrackVectorPosition();
10927  int FirstELinkPos = TempPDE.GetELinkPos();
10928  while(TempPDE.GetTrackVectorPosition() != (unsigned int)(NewTrain->LeadElement))
10929  {
10930  AllRoutes->RemoveRouteElement(16, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10931  TempPDE = AllRoutes->GetFixedRouteAt(182, RouteNumber).GetFixedPrefDirElementAt(195, 0);
10932  }
10933  if(TempPDE.GetTrackVectorPosition() == (unsigned int)(NewTrain->LeadElement))
10934  {
10935  AllRoutes->RemoveRouteElement(17, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10936  // remove the last element under LeadElement
10937  }
10938  AllRoutes->RebuildRailwayFlag = true;
10939  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode
10940  // now deal with a rear linked autosigs route
10941  if(Track->TrackElementAt(820, FirstTVPos).Conn[FirstELinkPos] > -1)
10942  {
10943  int LinkedRouteNumber = -1;
10944  if(AllRoutes->GetRouteTypeAndNumber(17, Track->TrackElementAt(821, FirstTVPos).Conn[FirstELinkPos],
10945  Track->TrackElementAt(822, FirstTVPos).ConnLinkPos[FirstELinkPos], LinkedRouteNumber) == TAllRoutes::AutoSigsRoute)
10946  {
10947  AllRoutes->GetFixedRouteAt(169, LinkedRouteNumber).SetRouteSignals(0);
10948  // this is ok as here we are setting signals from the start of the route
10949  }
10950  }
10951  SignalsSet = true;
10952  }
10953  }
10954  if(NewTrain->MidElement > -1)
10955  // if entering at a continuation MidElement == -1
10956  {
10957  // this is included in case a train starts with LeadElement on no route and MidElement on a route
10958  if(!SignalsSet)
10959  {
10960  RouteNumber = -1;
10961  if(AllRoutes->GetRouteTypeAndNumber(14, NewTrain->MidElement, NewTrain->MidEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute)
10962  {
10963  // below added in place of SetRouteSignals in v2.4.0 as don't want to set signals from start of route for a new train addition
10964  int RouteStartPosition;
10965  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
10966  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(22, Track->TrackElementAt(957, RearPosition).HLoc,
10967  Track->TrackElementAt(958, RearPosition).VLoc, SecondPair);
10968  if(FirstPair.first == RouteNumber)
10969  {
10970  RouteStartPosition = FirstPair.second;
10971  }
10972  else if(SecondPair.first == RouteNumber)
10973  {
10974  RouteStartPosition = SecondPair.second;
10975  }
10976  else
10977  {
10978  throw Exception("Error, RouteNumber not found in Route2MultiMap in 2nd of 2 calls to SetAllRearwardsSignals in AddTrain");
10979  }
10980  AllRoutes->SetAllRearwardsSignals(11, 0, RouteNumber, RouteStartPosition);
10981  SignalsSet = true;
10982  // AllRoutes->GetFixedRouteAt(, RouteNumber).SetRouteSignals(); above substituted in v2.4.0
10983  }
10984  else if(RouteNumber > -1) // non-autosigsroute
10985  {
10986  TPrefDirElement TempPDE = AllRoutes->GetFixedRouteAt(184, RouteNumber).GetFixedPrefDirElementAt(196, 0);
10987  int FirstTVPos = TempPDE.GetTrackVectorPosition();
10988  int FirstELinkPos = TempPDE.GetELinkPos();
10989  while(TempPDE.GetTrackVectorPosition() != (unsigned int)(NewTrain->MidElement))
10990  {
10991  AllRoutes->RemoveRouteElement(18, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10992  TempPDE = AllRoutes->GetFixedRouteAt(185, RouteNumber).GetFixedPrefDirElementAt(197, 0);
10993  }
10994  if(TempPDE.GetTrackVectorPosition() == (unsigned int)(NewTrain->MidElement))
10995  {
10996  AllRoutes->RemoveRouteElement(19, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10997  // remove the last element under LeadElement
10998  }
10999  AllRoutes->RebuildRailwayFlag = true;
11000  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode
11001  // now deal with a rear linked autosigs route
11002  if(Track->TrackElementAt(823, FirstTVPos).Conn[FirstELinkPos] > -1)
11003  {
11004  int LinkedRouteNumber = -1;
11005  if(AllRoutes->GetRouteTypeAndNumber(19, Track->TrackElementAt(824, FirstTVPos).Conn[FirstELinkPos],
11006  Track->TrackElementAt(825, FirstTVPos).ConnLinkPos[FirstELinkPos], LinkedRouteNumber) == TAllRoutes::AutoSigsRoute)
11007  {
11008  AllRoutes->GetFixedRouteAt(170, LinkedRouteNumber).SetRouteSignals(1);
11009  // this is ok as now we are setting signals from the start of the route
11010  }
11011  }
11012  }
11013  }
11014  }
11015  TrainVector.push_back(*NewTrain);
11016  Utilities->CallLogPop(731);
11017  return(true);
11018 }
11019 
11020 // ---------------------------------------------------------------------------
11021 
11022 int TTrainController::EntryPos(int Caller, int TrainIDIn, int TrackVectorNumber)
11023 {
11024  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",EntryPos," + AnsiString(TrainIDIn) + "," +
11025  AnsiString(TrackVectorNumber));
11026  int VecPos = -1;
11027 
11028  for(unsigned int x = 0; x < TrainVector.size(); x++)
11029  {
11030  if(TrainVectorAt(1, x).TrainID == TrainIDIn)
11031  {
11032  VecPos = x;
11033  }
11034  }
11035  if(VecPos == -1)
11036  {
11037  throw Exception("Error, VecPos not set in EntryPos");
11038  }
11039  if(TrainVectorAt(2, VecPos).LeadElement == TrackVectorNumber)
11040  {
11041  Utilities->CallLogPop(734);
11042  return(TrainVectorAt(3, VecPos).LeadEntryPos);
11043  }
11044  else if(TrainVectorAt(4, VecPos).MidElement == TrackVectorNumber)
11045  {
11046  Utilities->CallLogPop(735);
11047  return(TrainVectorAt(5, VecPos).MidEntryPos);
11048  }
11049  else if(TrainVectorAt(6, VecPos).LagElement == TrackVectorNumber)
11050  {
11051  Utilities->CallLogPop(736);
11052  return(TrainVectorAt(7, VecPos).LagEntryPos);
11053  }
11054  Utilities->CallLogPop(737);
11055  return(-1);
11056 }
11057 
11058 // ---------------------------------------------------------------------------
11059 
11061 {
11062  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainVectorAtIdent," + AnsiString(TrainID));
11063  for(unsigned int x = 0; x < TrainVector.size(); x++)
11064  {
11065  if(TrainVectorAt(53, x).TrainID == TrainID)
11066  {
11067  Utilities->CallLogPop(738);
11068  return(TrainVectorAt(54, x));
11069  }
11070  }
11071  throw Exception("Error - No Train identified in TrainVectorAtIdent with ID = " + AnsiString(TrainID));
11072 }
11073 
11074 // ---------------------------------------------------------------------------
11075 
11076 bool TTrainController::TrainExistsAtIdent(int Caller, int TrainID)
11077 // return true if find the train (added at v2.4.0 as can select a removed train in
11078 // ActionsDueListBox before it updates - reported by LiWinDom in error report 23/04/20)
11079 {
11080  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainExistsAtIdent," + AnsiString(TrainID));
11081  for(unsigned int x = 0; x < TrainVector.size(); x++)
11082  {
11083  if(TrainVectorAt(69, x).TrainID == TrainID)
11084  {
11085  Utilities->CallLogPop(2152);
11086  return(true);
11087  }
11088  }
11089  Utilities->CallLogPop(2153);
11090  return(false);
11091 }
11092 
11093 // ---------------------------------------------------------------------------
11094 
11095 TDateTime TTrainController::GetControllerTrainTime(int Caller, TDateTime Time, int RepeatNumber, int IncrementalMinutes)
11096 {
11097  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetControllerTrainTime," + AnsiString(RepeatNumber) + "," +
11098  Utilities->Format96HHMMSS(Time));
11099  TDateTime RepeatTime = TrainController->GetRepeatTime(47, Time, RepeatNumber, IncrementalMinutes);
11100 
11101  Utilities->CallLogPop(2061);
11102  return(RepeatTime);
11103 }
11104 
11105 // ---------------------------------------------------------------------------
11106 
11107 AnsiString TTrainController::ContinuationEntryFloatingTTString(int Caller, TTrainDataEntry *TTDEPtr, int RepNum, int IncMins, int IncDig)
11108 // Enter with Ptr pointing to first action to be listed (i.e. next action)
11109 {
11110  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ContinuationEntryFloatingTTString" + "," + TTDEPtr->HeadCode);
11111  AnsiString RetStr = "", PartStr = "", MinMinsString;
11112  int Count = 0;
11113  TActionVectorIterator Ptr = TTDEPtr->ActionVector.begin();
11114 
11115  Ptr--; // because incremented at start of loop
11116  do
11117  {
11118  Ptr++;
11119  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
11120  {
11121  continue; // move past the starting entry
11122  }
11123  if((Ptr->FormatType == Repeat) || Ptr >= TTDEPtr->ActionVector.end())
11124  {
11125  break;
11126  }
11127  if(Ptr->SignallerControl)
11128  {
11129  RetStr = "Train under signaller control";
11130  break;
11131  }
11132  if(Ptr->FormatType == TimeTimeLoc)
11133  {
11134  if(Ptr->ArrivalTime == Ptr->DepartureTime) //arrive & dep shown in single entry
11135  {
11136  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
11137  {
11138  double MDTdouble = Ptr->MinDwellTime / 60;
11139  double MDT = int(MDTdouble * 10);
11140  MDT = MDT / 10;
11141  MinMinsString = "mins";
11142  if((MDT < 1.1) && (MDT > 0.9))
11143  {
11144  MinMinsString = "min";
11145  }
11146  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(34, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive & depart from " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
11147  }
11148  else
11149  {
11150  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(86, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive & depart from " + Ptr->LocationName;
11151  }
11152  }
11153  else
11154  {
11155  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
11156  {
11157  double MDTdouble = Ptr->MinDwellTime / 60;
11158  double MDT = int(MDTdouble * 10);
11159  MDT = MDT / 10;
11160  MinMinsString = "mins";
11161  if((MDT < 1.1) && (MDT > 0.9))
11162  {
11163  MinMinsString = "min";
11164  }
11165  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(16, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')' + '\n' +
11166  Utilities->Format96HHMM(GetControllerTrainTime(85, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
11167  }
11168  else
11169  {
11170  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(80, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName + '\n' +
11171  Utilities->Format96HHMM(GetControllerTrainTime(84, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
11172  }
11173  Count++; // because there are 2 entries
11174  }
11175  }
11176  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1))) //arrival
11177  {
11178  if(Ptr->MinDwellTime > 30.1) //add 0.1 to avoid rounding errors
11179  {
11180  double MDTdouble = Ptr->MinDwellTime / 60;
11181  double MDT = int(MDTdouble * 10);
11182  MDT = MDT / 10;
11183  MinMinsString = "mins";
11184  if((MDT < 1.1) && (MDT > 0.9))
11185  {
11186  MinMinsString = "min";
11187  }
11188  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(87, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName + " (Min. Dwell Time " + MDT + MinMinsString + ')';
11189  }
11190  else
11191  {
11192  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(35, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName;
11193  }
11194  }
11195  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1))) //departure
11196  {
11197  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(4, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
11198  }
11199  else if(Ptr->FormatType == PassTime) // new
11200  {
11201  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(5, Ptr->EventTime, RepNum, IncMins)) + ": Pass " + Ptr->LocationName;
11202  }
11203  else if(Ptr->Command == "Fns")
11204  {
11205  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(6, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11206  TrainController->GetRepeatHeadCode(46, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11207  PartStr = ControllerGetNewServiceDepartureInfo(11, Ptr, RepNum, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to PartStr
11208  }
11209  else if(Ptr->Command == "F-nshs")
11210  {
11211  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(7, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11212  Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName;
11213  PartStr = ControllerGetNewServiceDepartureInfo(13, Ptr, 0, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
11214  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
11215  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
11216  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
11217  }
11218 //Since this is a new continuation entry service it can't be Fns-sh or Frh-sh but leave these in for consistency with TTrain::FloatingTimetableString
11219  else if((Ptr->Command == "Fns-sh") && (RepNum < (TTDEPtr->NumberOfTrains - 1))) // not the last repeat number
11220  {
11221  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(8, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11222  TrainController->GetRepeatHeadCode(47, Ptr->OtherHeadCode, RepNum + 1, IncDig) + " at " + Ptr->LocationName;
11223  // use RepNum+1 because it's the repeat number of the NEXT shuttle service that is relevant
11224  PartStr = ControllerGetNewServiceDepartureInfo(15, Ptr, RepNum + 1, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
11225  }
11226  else if((Ptr->Command == "Fns-sh") && (RepNum >= (TTDEPtr->NumberOfTrains - 1))) // last repeat number
11227  {
11228  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(9, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11229  Ptr->NonRepeatingShuttleLinkHeadCode, +" at " + Ptr->LocationName;
11230  PartStr = ControllerGetNewServiceDepartureInfo(17, Ptr, 0, TTDEPtr, Ptr->NonRepeatingShuttleLinkEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
11231  }
11232  else if((Ptr->Command == "Frh-sh") && (RepNum < (TTDEPtr->NumberOfTrains - 1))) // not the last repeat number
11233  {
11234  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(10, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11235  TrainController->GetRepeatHeadCode(48, Ptr->OtherHeadCode, RepNum + 1, IncDig) + " at " + Ptr->LocationName;
11236  // use RepNum+1 because it's the repeat number of the NEXT shuttle service that is relevant
11237  PartStr = ControllerGetNewServiceDepartureInfo(19, Ptr, RepNum + 1, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
11238  }
11239  else if((Ptr->Command == "Frh-sh") && (RepNum >= (TTDEPtr->NumberOfTrains - 1))) // last repeat number
11240  {
11241  PartStr = "Terminate at " + Ptr->LocationName;
11242  }
11243  else if(Ptr->Command == "Frh")
11244  {
11245  PartStr = "Terminate at " + Ptr->LocationName;
11246  }
11247  else if(Ptr->Command == "Fer")
11248  {
11249  AnsiString AllowedExits;
11250  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(11, Ptr->EventTime, RepNum, IncMins)) + ": Exit railway" +
11251  TrainController->GetExitLocationAndAt(3, Ptr->ExitList, AllowedExits) + AllowedExits;
11252  }
11253  else if(Ptr->Command == "Fjo")
11254  {
11255  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(12, Ptr->EventTime, RepNum, IncMins)) + ": Join " + TrainController->GetRepeatHeadCode(49,
11256  Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11257  }
11258  else if(Ptr->Command == "jbo")
11259  {
11260  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(13, Ptr->EventTime, RepNum, IncMins)) + ": Joined by " + TrainController->GetRepeatHeadCode
11261  (50, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11262  }
11263  else if(Ptr->Command == "fsp")
11264  {
11265  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(14, Ptr->EventTime, RepNum, IncMins)) + ": Front split to " +
11266  TrainController->GetRepeatHeadCode(51, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11267  }
11268  else if(Ptr->Command == "rsp")
11269  {
11270  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(15, Ptr->EventTime, RepNum, IncMins)) + ": Rear split to " +
11271  TrainController->GetRepeatHeadCode(52, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11272  }
11273  else if(Ptr->Command == "cdt")
11274  {
11275  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(88, Ptr->EventTime, RepNum, IncMins)) + ": Change direction at " + Ptr->LocationName;
11276  }
11277  else if(Ptr->Command == "dsc")
11278  {
11279  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(27, Ptr->EventTime, RepNum, IncMins)) + ": Change description at " + Ptr->LocationName;
11280  }
11281  else if(Ptr->Command == "cms")
11282  {
11283  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(32, Ptr->EventTime, RepNum, IncMins)) + ": Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName;
11284  }
11285  if(RetStr != "")
11286  {
11287  RetStr = RetStr + '\n' + PartStr;
11288  }
11289  else
11290  {
11291  RetStr = PartStr;
11292  }
11293  Count++;
11294  }
11295  while(Ptr < TTDEPtr->ActionVector.end() && (Count < 33) && ((Ptr->Command == "") || ((Ptr->Command != "") && (Ptr->Command[1] != 'F'))));
11296  // limit of 33 allows a max of 34 entries (may have gone from 32 to 34 because of a TimeTimeLoc), which with track and train status gives
11297  // a max of 48 lines, at 13 pixels each, = 624 pixels & screen height has 641 so will fit comfortably. Also 34 timetable entries is as far
11298  // forward as anyone should wish to see without looking at the full timetable
11299  Utilities->CallLogPop(2072);
11300  return(RetStr);
11301 }
11302 
11303 // ---------------------------------------------------------------------------
11304 
11306  TTrainDataEntry *LinkedTrainDataPtr, int IncrementalMinutes, int IncrementalDigits, AnsiString RetStr)
11307 { //no delays as train not entered yet //above added IncrementalDigits at v2.18.0 for GetRepeatHeadCode
11308  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - TDEPtr->ActionVector.begin()) + ","
11309  + AnsiString(RptNum) + ",ControllerGetNewServiceDepartureInfo," + TDEPtr->HeadCode);
11310  AnsiString DepTime = "", EventTime = "";
11311  bool CDTFlag = false; //reports if train changes direction before departs
11312  TActionVector NewServiceAV = LinkedTrainDataPtr->ActionVector;
11313  AnsiString CurrentLocation = NewServiceAV.at(0).LocationName; //added at v2.12.0 to show departure direction
11314  AnsiString TowardsLocation = ""; //added at v2.12.0 to show departure direction
11315  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++) //added at v2.12.0 to obtain departure direction
11316  {
11317  if((AVI->LocationName != CurrentLocation) && (AVI->LocationName != "") && (TowardsLocation == ""))
11318  {
11319  TowardsLocation = AVI->LocationName;
11320  }
11321  else if((AVI->Command == "Fer") && (TowardsLocation == "") && !AVI->ExitList.empty())
11322  {
11323  TTrackElement TE = Track->TrackElementAt(1453, (AVI->ExitList.front()));
11324  if(TE.ActiveTrackElementName != "")
11325  {
11326  TowardsLocation = TE.ActiveTrackElementName;
11327  }
11328  else
11329  {
11330  TowardsLocation = AnsiString("track element ID ") + TE.ElementID;
11331  }
11332  }
11333  }
11334  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++)
11335  {
11336  if(AVI->Command == "cdt")
11337  {
11338  CDTFlag = !CDTFlag; //toggles flag - allows for there being more than one cdt before departure
11339  continue;
11340  }
11341  if((AVI->Command == "fsp") || (AVI->Command == "rsp"))
11342  {
11343  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(21, AVI->EventTime, RptNum, IncrementalMinutes));
11344  RetStr += "\nNew service splits at " + EventTime;
11345  Utilities->CallLogPop(2237);
11346  return(RetStr);
11347  }
11348  if(AVI->Command == "jbo")
11349  {
11350  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(30, AVI->EventTime, RptNum, IncrementalMinutes));
11351  RetStr += "\nNew service joined by " + GetRepeatHeadCode(70, AVI->OtherHeadCode, RptNum, IncrementalDigits) + " at " + EventTime;
11352  Utilities->CallLogPop(2238); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
11353  return(RetStr);
11354  }
11355  if((AVI->Command == "Fns") || (AVI->Command == "F-nshs") || (AVI->Command == "Fns-sh"))
11356  {
11357  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(31, AVI->EventTime, RptNum, IncrementalMinutes));
11358  RetStr += "\nNew service finishes and forms another new service at " + EventTime;
11359  Utilities->CallLogPop(2607);
11360  return(RetStr);
11361  }
11362  if(AVI->Command == "Fjo")
11363  {
11364  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(22, AVI->EventTime, RptNum, IncrementalMinutes));
11365  RetStr += "\nNew service finishes and joins " + TrainController->GetRepeatHeadCode(71, AVI->OtherHeadCode, RptNum, IncrementalDigits) + " at " + EventTime;
11366  Utilities->CallLogPop(2608); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
11367  return(RetStr);
11368  }
11369  if(AVI->Command == "Frh")
11370  {
11371  RetStr += "\nNew service finishes and remains at location.";
11372  Utilities->CallLogPop(2609);
11373  return(RetStr);
11374  }
11375  if((AVI->FormatType == TimeLoc) && (AVI->DepartureTime > TDateTime(-1))) //departure time set
11376  {
11377  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(23, AVI->DepartureTime, RptNum, IncrementalMinutes));
11378  if(CDTFlag)
11379  {
11380  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
11381  {
11382  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at " + DepTime;
11383  }
11384  else
11385  {
11386  RetStr += "\nNew service changes direction then departs at " + DepTime;
11387  }
11388  }
11389  else
11390  {
11391  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
11392  {
11393  RetStr += "\nNew service departs towards " + TowardsLocation + " at " + DepTime;
11394  }
11395  else
11396  {
11397  RetStr += "\nNew service departs at " + DepTime;
11398  }
11399  }
11400  Utilities->CallLogPop(2239);
11401  return(RetStr);
11402  }
11403  }
11404  Utilities->CallLogPop(2223);
11405  return(RetStr);
11406 }
11407 
11408 // ---------------------------------------------------------------------------
11409 // $$$$$$$$$$$$$$$$$$$$$$ Start of Timetable Functions $$$$$$$$$$$$$$$$$$$$$$$$
11410 /*
11411  Note: The terms 'action' and 'entry' have been used freely for individual code lines within services in comments & in variable names, but
11412  for messages and in the manual and help files the term Entry is reserved for a complete service or train (i.e. an entry in the timetable),
11413  and 'event' is reserved for and individual code line within a service. Repeats use the term 'item' if they use any at all.
11414 
11415  In references to 'HeadCode' can have an optional prefix - up to 4 additional characters that can be anything, so long as last 4 digits
11416  represent the headcode. This allows links to be uniquely identified regardless of the headcode - so can have same headcodes as often as
11417  user wishes
11418 
11419  Prior to start time, anything except a line beginning with a time [...leading spaces...] HH:MM is ignored - can be
11420  descriptive text or anything user wishes
11421  A time on its own line [HH:MM], with or without leading spaces, but with anything following it before the CR (which will
11422  be ignored) is taken as the timetable start time.
11423  Thereafter there must be text on every line in the timetable, as the first blank line (or end of file) will be taken as the end of the
11424  timetable. Text can follow the 'end of timetable' blank line if the user wishes.
11425  A line within the timetable beginning with '*', with or without leading spaces, is ignored. Such lines can add text
11426  within the timetable if required.
11427  Timetable entries consist of one line per headcode (i.e. per service, not necessarily per train, as one train can run several different
11428  services)
11429  Each line starts with HeadCode & full train information for a new train (Snt or Snt-sh), or, for a continuing service
11430  (Sfs, Sns, Sns-sh or Sns-fsh), can have (a) Headcode only or (b) HeadCode + Description, nothing else
11431 
11432  All leading & trailing spaces before & after a line or any entry in a line are stripped off - these can be included to make reading a
11433  text timetable file easier
11434 
11435  form:-
11436  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
11437  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
11438  then multiple entries, separated by commas, of the form:-
11439 
11440  HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
11441  HH:MM;Snt-sh;RearStartIdent FrontStartIdent;Fsh HeadCode }SNTShuttle }
11442  HH:MM;Sns-sh;Fxx-sh HeadCode;F-nshs HeadCode (non-repeating)}SNSShuttle }
11443 
11444  HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode } Train action entries
11445  HH:MM;F-nshs;NonRepeatingShuttleLinkHeadCode }FNSNonRepeatToShuttle }
11446  HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle }
11447 
11448  HH:MM;Command (cdt) }TimeCmd }
11449  HH:MM;Command;Description (dsc) }TimeCmdDescription }
11450  HH:MM;Command;NewMaxSpeed (cms) }TimeCmdMaxSpeed }
11451  HH:MM;Location (arr & dep) }TimeLoc }
11452  HH:MM;HH:MM;Location }TimeTimeLoc }
11453  HH:MM;pas;Location }PassTime }
11454  HH:MM;Fns-sh;Snx-sh HeadCode;Sns-fsh HeadCode (non-rep) }FSHNewService }
11455  HH:MM;Fer;set of allowable IDs }ExitRailway }
11456  Command (Frh only) }FinRemHere }
11457 
11458  R;mm;dd;nn. Repeat Repeat entry
11459 
11460  Formats:
11461 
11462  Command only: Frh
11463  Time;Command: cdt
11464  Time;Command;Description dsc
11465  Time;Command;MaxSpeed msc
11466  Time;Command;Headcode: Sfs Sns jbo fsp rsp Fns Fjo Frh-sh F-nshs Sns-fsh
11467  Time;Command;2 Element IDs: Snt
11468  Time;Comand;n Element IDs: Fer
11469  Time;Command;rep Headcode;nonrep Headcode: Sns-sh Fns-sh
11470  Time;Command;2 Element IDs;Headcode Snt-sh
11471  Time;Command;Location pas
11472  Time;Location Arr Dep
11473  Time;Time;Location Arr & dep together
11474 
11475  11 Non-linked entries: Snt (located & unlocated); pas; cdt; dsc; msc; TimeLoc arr & dep; TimeTimeLoc; Fer; Frh
11476 
11477  9 1x Linked entries: Non-shuttle: fsp or rsp -> Sfs; Fns -> Sns; Fjo -> jbo; times must match, headcodes must match
11478  Shuttle: F-nshs -> Sns-sh: times match, F-nshs HeadCode matches Sns-sh 2nd Headcode;
11479  Fns-sh -> Sns-fsh: Fns-sh time + all repeats = Sns-fsh time, Fns-sh 2nd headcode matches Sns-fsh Headcode
11480 
11481  4 2x Linked entries, all shuttles:
11482 
11483  Frh-sh -> Snt-sh: Frh-sh time = Snt-sh time + 1 repeat while repeating, Frh-sh Headcode = Snt-sh Headcode;
11484  -> Sns-sh: Frh-sh time = Sns-sh time + 1 repeat while repeating, Frh-sh Headcode = Sns-sh 1st Headcode;
11485  -> Remain Here (at finish location after all repeats)
11486  Fns-sh -> Snt-sh: Frh-sh time = Snt-sh time + 1 repeat while repeating, Fns-sh 1st Headcode = Snt-sh Headcode
11487  -> Sns-sh: Frh-sh time = Sns-sh time + 1 repeat while repeating, Fns-sh 1st Headcode = Sns-sh 1st Headcode
11488 
11489  Moving/AtLoc states:-
11490 
11491  Successor state Type
11492 
11493  Snt located AtLoc ) Snt AtLoc successors: TimeLoc dep/jbo/fsp/rsp/cdt/dsc/msc/Frh/Fns/Fjo/Frh-sh/Fns-sh/F-nshs;
11494  Snt Unlocated Moving ) Snt Moving successors: TimeLoc arr/TimeTimeLoc/pas/Fer;
11495  Sfs AtLoc )
11496  Sns AtLoc ) Start
11497  Sns-fsh AtLoc )
11498  Snt-sh AtLoc )
11499  Sns-sh AtLoc )
11500 
11501  pas Moving )
11502  jbo AtLoc )
11503  fsp AtLoc )
11504  rsp AtLoc ) Intermediate
11505  cdt AtLoc )
11506  dsc AtLoc )
11507  msc AtLoc )
11508  TimeLoc arr Moving (bef), AtLoc (aft) )
11509  TimeLoc dep AtLoc (bef), Moving (aft) )
11510  TimeTimeLoc Moving )
11511 
11512  Fns Repeat/Nothing)
11513  Fjo Repeat/Nothing)
11514  Frh Repeat/Nothing)
11515  Fer Repeat/Nothing) Finish
11516  Frh-sh Repeat )
11517  Fns-sh Repeat )
11518  F-nshs Nothing )
11519 
11520  Descriptions:
11521  Snt New train
11522  Sfs New service from split
11523  Sns New service from another service
11524  Sns-fsh New non-repeating service from a shuttle service
11525  Snt-sh New shuttle train at a timetabled stop
11526  Sns-sh New shuttle service from a feeder service
11527 
11528  pas Pass
11529  jbo Be joined by another train
11530  fsp Front split
11531  rsp Rear split
11532  cdt Change direction of train
11533  dsc Change description of train
11534  msc Change max speed of train
11535  TimeLoc arr Arrival
11536  TimeLoc dep Departure
11537  TimeTimeLoc Arrival and departure
11538 
11539  Fns Finish & form a new service
11540  Fjo Finish & join another train
11541  Frh Finish & remain here
11542  Fer Finish & exit railway
11543  Frh-sh Finish & repeat shuttle, finally remain here
11544  Fns-sh Finish & repeat shuttle, finally form a non-repeating service
11545  F-nshs Finish & form a shuttle feeder service
11546 */
11547 
11548 bool TTrainController::TimetableIntegrityCheck(int Caller, char *FileName, bool GiveMessages, bool CheckLocationsExistInRailway) // true for success
11549 {
11550  // Error messages mainly given in called functions, five are given here - empty file; inability to find a start time; timetable containing
11551  // a line that is too long; timetable containing too few lines; and timetable failed to open.
11552  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TimetableIntegrityCheck," + AnsiString(FileName));
11553  // new for v0.2b
11554  // compile ActiveTrackElementNameMap
11555  TTrack::TActiveTrackElementNameMapEntry ActiveTrackElementNameMapEntry;
11556 
11558  for(unsigned int x = 0; x < Track->TrackVector.size(); x++)
11559  {
11560  // if((Track->TrackElementAt(, x).ActiveTrackElementName != "") && (Track->TrackElementAt(, x).TrackType != Continuation))
11562  == Track->ContinuationNameMap.end())
11563  {
11564  // exclude any name that appears in a continuation, error message given in tt validation if try to include such a name in a tt
11565  ActiveTrackElementNameMapEntry.first = Track->TrackElementAt(1035, x).ActiveTrackElementName;
11566  ActiveTrackElementNameMapEntry.second = 0; // this is a dummy value
11567  Track->ActiveTrackElementNameMap.insert(ActiveTrackElementNameMapEntry);
11568  }
11569  }
11571  // end of new section
11572  std::ifstream TTBLFile(FileName, std::ios_base::binary);
11573 
11574  // binary mode so the "\r\n" pairs stay as they are rather than being entered as '\n'
11575  if(TTBLFile.is_open())
11576  {
11577  char *TrainTimetableString = new char[10000];
11578  // enough for over 200 stations, should be adequate!
11579  bool EndOfFile = false;
11580  int Count = 0;
11581  // counts 'relevant' lines, i.e ignores any before the start time on its own line
11582  TTBLFile.getline(TrainTimetableString, 10000, '\0');
11583  // delimiter is '\0' as it's an AnsiString
11584  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
11585  // file empty - stores a null in 1st position if doesn't load any characters
11586  {
11587  // may still have eof even if read a line (no CRLF at end), and
11588  // if so need to process it
11589  TimetableMessage(GiveMessages, "Timetable invalid - file empty");
11590  TTBLFile.close();
11591  delete[] TrainTimetableString;
11592  Utilities->CallLogPop(1611);
11593  return(false);
11594  }
11595  AnsiString OneLine(TrainTimetableString);
11596  bool FinalCallFalse = false;
11597  while((Count == 0) && !ProcessOneTimetableLine(5, Count, OneLine, EndOfFile, FinalCallFalse, GiveMessages, CheckLocationsExistInRailway))
11598  // get rid of lines before the start time
11599  {
11600  // ProcessOneTimetableLine returns true for a valid start time, an EndOfFile &/or a blank entry
11601  TTBLFile.getline(TrainTimetableString, 10000, '\0');
11602  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
11603  // stores a null in 1st position if doesn't load any characters
11604  {
11605  // may still have eof even if read a line (no CRLF at end), and
11606  // if so need to process it
11607  TimetableMessage(GiveMessages, "Timetable invalid - unable to find a valid start time on its own line");
11608  TTBLFile.close();
11609  delete[] TrainTimetableString;
11610  Utilities->CallLogPop(772);
11611  return(false);
11612  }
11613  OneLine = AnsiString(TrainTimetableString);
11614  }
11615  // here when have accepted the start time
11616  Count++; // increment past the start time
11617  while(!EndOfFile)
11618  {
11619  TTBLFile.getline(TrainTimetableString, 10000, '\0');
11620  // get next line after start time
11621  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
11622  // stores a null in 1st position if doesn't load any characters
11623  {
11624  // may still have eof even if read a line (no CRLF at end), and
11625  // if so need to process it
11626  EndOfFile = true;
11627  OneLine = "";
11628  }
11629  else
11630  {
11631  OneLine = AnsiString(TrainTimetableString);
11632  }
11633  if(OneLine.Length() > 9999)
11634  {
11635  TimetableMessage(GiveMessages, "Timetable contains a line that is too long - 10,000 or more characters!");
11636  TTBLFile.close();
11637  delete[] TrainTimetableString;
11638  Utilities->CallLogPop(789);
11639  return(false);
11640  }
11641  bool FinalCallFalse = false;
11642  if(!ProcessOneTimetableLine(6, Count, OneLine, EndOfFile, FinalCallFalse, GiveMessages, CheckLocationsExistInRailway))
11643  // false for FinalCall - just checking at this stage
11644  {
11645  TTBLFile.close();
11646  delete[] TrainTimetableString;
11647  Utilities->CallLogPop(770);
11648  return(false);
11649  }
11650  if(EndOfFile && (Count < 2))
11651  // Timetable must contain at least two relevant lines, one for start time and at least one train
11652  {
11653  TimetableMessage(GiveMessages, "Timetable has too few or no relevant entries - must have a start time on its own line and at least one train");
11654  TTBLFile.close();
11655  delete[] TrainTimetableString;
11656  Utilities->CallLogPop(771);
11657  return(false);
11658  }
11659  Count++;
11660  }
11661  delete[] TrainTimetableString;
11662  TTBLFile.close();
11663  } // if(TTBLFile.is_open())
11664  else
11665  {
11666  TimetableMessage(GiveMessages, "Failed to open timetable file, make sure it's spelled correctly, it exists and isn't open in another application");
11667  Utilities->CallLogPop(2154);
11668  return(false);
11669  }
11670  Utilities->CallLogPop(753);
11671  return(true);
11672 }
11673 
11674 // ---------------------------------------------------------------------------
11675 
11676 bool TTrainController::ProcessOneTimetableLine(int Caller, int Count, AnsiString OneLine, bool &EndOfFile, bool FinalCall, bool GiveMessages,
11677  bool CheckLocationsExistInRailway) // return true for success
11678 
11679 /* Format:
11680  Prior to start time, anything except a line beginning with a time [...leading spaces...] HH:MM is ignored - can be
11681  descriptive text or anything user wishes
11682  A time on its own line [HH:MM], with or without leading spaces, but with anything following it before the CR (which will
11683  be ignored) is taken as the timetable start time.
11684  Thereafter there must be text on every line in the timetable, as the first blank line (or end of file) will be taken as the end of the
11685  timetable. Text can follow the 'end of timetable' blank line if the user wishes.
11686  A line within the timetable beginning with '*', with or without leading spaces, is ignored. Such lines can add text
11687  within the timetable if required.
11688  Timetable entries consist of one line per headcode (i.e. per service, not necessarily per train, as one train can run several different
11689  services)
11690  Each line starts with HeadCode & full train information for a new train (Snt or Snt-sh), or, for a continuing service
11691  (Sfs, Sns, Sns-sh or Sns-fsh), can have (a) Headcode only or (b) HeadCode + Description, nothing else
11692 
11693  All leading & trailing spaces before & after a line or any entry in a line are stripped off - these can be included to make reading a
11694  text timetable file easier
11695 
11696  form:-
11697  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
11698  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
11699  then multiple entries, separated by commas, of the form:-
11700 
11701  Format FormatType
11702  [W]HH:MM;Command (cdt) }TimeCmd }
11703  [W]HH:MM;dsc;new description }TimeCmdDescription }
11704  [W]HH:MM;cms;new max speed }TimeCmdMaxSpeed }
11705  [W]HH:MM;Fer;set of allowable IDs }ExitRailway }
11706  [W]HH:MM;pas;Location }PassTime }
11707  [W]HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
11708  [W]HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode }
11709  [W]HH:MM;F-nshs;non-repeating headcode }FNSNonRepeatToShuttle }
11710  [W]HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle } Train action entries
11711  [W]HH:MM;Snt-sh;RearStartIdent FrontStartIdent;FSH HeadCode }SNTShuttle }
11712  [W]HH:MM;Sns-sh;FSH HeadCode;F-nshs HeadCode (non-repeating) }SNSShuttle }
11713  [W]HH:MM;Fns-sh;Details }FSHNewService }
11714  [W]HH:MM;Location (arr & dep) }TimeLoc }
11715  [W]HH:MM;HH:MM;Location }TimeTimeLoc }
11716  Command (Frh only) }FinRemHere }
11717 
11718  R;mm;dd;nn. Repeat Repeat entry
11719 
11720  Two times represent arrival & departure, without any other events between (if arrival and departure times are the same
11721  then departure is 30 sec after arrival), single time represents (a) event time; (b) arrival time if train not already
11722  at location; or (c) departure time if train already at location (including train started at location either as a new
11723  train or as a continuation service train at that location). All lines must contain a start entry and a finish entry,
11724  the finish being the last unless there is a repeat entry. The repeat entry begins with 'R', then the incremental
11725  minutes, incremental train headcode last 2 digits, and number of repeats.
11726 
11727  Shuttle entries are where can loop back to an earlier Snt-sh or Sns-sh entry from a Frh-sh or Fns-sh (Finish Shuttle)
11728  entry. Here the shuttle start can have two entries, one from a set position (Snt-sh, must be located) or from a F-nshs
11729  (Sns-sh) - with NO repeat from this source, and from a Fxx-sh, with repeats. After all shuttle repeats Frh-sh remains
11730  where it is, and Fns-sh links to a new service (via an Sns entry), but there must be no repeats in this new service
11731  (it's for a shuttle train to return to depot at end of services)
11732 
11733  Command/Location & details are as follows:-
11734 
11735  Although headcodes can be duplicated, all joins, splits, new services etc give other headcode from both trains' povs, and
11736  these have to match once only, i.e. if 2E44 splits to 2E45 then it can't split to 2E45 anywhere else, and 2E45 must give
11737  2E44 in its Sfs entry. All these are checked.
11738  ***add note re shuttles & their use of otherheadcodes + non-repeating headcodes***
11739 
11740  Start commands:-
11741  Snt (StartNew) = Start New Train, i.e. create new train, details = rearstartident, space, frontstartident (can't confuse
11742  with loc as a start entry can't have a location as details)
11743  Sfs (TimeCmdHeadCode) = Start From Split, create a new train that has split from another train (& listed in other train's
11744  timetable line), details = other headcode - (can't confuse with loc as start can't be a loc)
11745  Sns (TimeCmdHeadCode) = Start, headcode change from earlier service - no need to create train as already exists, it just
11746  changes its relevant information, details = old headcode (can't confuse with loc as start can't be a loc)
11747  Snt-sh (SNTShuttle) = Start New Train, i.e. create new train, details = rearstartident, space, frontstartident (can't
11748  confuse with loc as start can't be a loc) then the Fsh-XX service headcode (OtherHeadCode can't be same headcode)
11749  Sns-sh (SNSShuttle) = Start, headcode change from earlier service - no need to create train as already exists, it just
11750  changes its relevant information, details = the FSH-XX service headcode (OtherHeadCode, can't be same headcode)
11751  followed by the non-repeating F-nshs headcode (NonRepeatingShuttleLinkHeadCode)
11752  Sns-fsh (SNSNonRepeatFromShuttle) = Start as a non-repeating service from a shuttle service that has finished all its
11753  repeats, details = NonRepeatingShuttleLinkHeadCode for the corresponding shuttle Fns-sh service
11754 
11755  Intermediate commands:-
11756  Time - Location (TimeLoc), can be arrival or departure depending on context
11757  Time Time location (TimeTimeLoc), arrival and departure
11758  Location Name (exactly as used in the railway) in TimeLoc & TimeTimeLoc means that the train is required to stop at the location
11759  pas (PassTime), Time;pas;Location
11760  jbo (TimeCmdHeadCode) = Joined By Other = joined by other train, details = new headcode (await other train - may be delayed). Note that the
11761  joining train's finish details must correspond or the file check will fail
11762  fsp (TimeCmdHeadCode) = Front Split = a new train splits away from front of this train, both trains in same direction, details = new headcode (create
11763  new train - that train's starting information must correspond)
11764  rsp (TimeCmdHeadCode) = Rear Split = a new train splits away from rear of this train, both trains in same direction, details = new headcode (create
11765  new train - that train's starting information must correspond)
11766  cdt (TimeCmd) = Change Direction of Train = change direction, no details needed & no train creation
11767  dsc (TimeCmdDescription) = Change description of train = new description only, no train creation
11768  cms (TimeCmdMaxSpeed) = Change maximum speed of train = new max speed only, no train creation
11769 
11770  Finish commands:-
11771  Fns (TimeCmdHeadCode) = Finish New Service = finish, form new service in same direction, details = new headcode (no train
11772  creation)
11773  F-nshs (FNSShuttle) = Finish New Service (Shuttle) = finish, form new shuttle service in same direction, details =
11774  shuttle headcode (no train creation)
11775  Fjo (TimeCmdHeadCode) = Finish Join Other = finish, join other train (which must be on an adjacent element, either end -
11776  may have to wait for it), details = new headcode (delete train)
11777  Frh (FinRemHere) = Finish Remain Here = stay here indefinitely, no details & no time needed
11778  Fer (ExitRailway) = Finish, exit railway (i.e at a continuation) - details = set of allowable exit IDs
11779  Frh-sh (TimeCmdHeadCode) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done remain
11780  here
11781  Fns-sh (FSHNewService) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done form new
11782  service via Sns-fsh using the NonRepeatingShuttleLinkHeadCode
11783 
11784  Repeat:-
11785  R;mm;dd;nn (Repeat) where mm = minute increment, dd = 2nd 2 headcode digit increment & nn = no. of repeats (no max as can duplicate
11786  headcodes - it is up to user to avoid duplicates if he/she wishes to.
11787 
11788  Checks carried out with error messages in this function:-
11789  At least one comma in a service line (it's based on a .csv file)
11790  No entries following train information;
11791  At least one comma in remainder after train information (i.e at least a start and a finish entry);
11792  SplitEntry returns false in an intermediate entry - message repeats the entry for information;
11793  First entry not a start entry;
11794  Train information incomplete before a start entry;
11795  Entry follows a finish entry but doesn't begin with 'R';
11796  SplitEntry returns false in a finish entry - message repeats the entry for information;
11797  Last action entry isn't a finish entry.
11798 
11799  Function returns false with no message if:-
11800  Timetable start time invalid (no message is given for an invalid time as the line is assumed to be an irrelevant line; if no start
11801  time is found at all then an error message is given in the calling function);
11802  SplitTrainInfo returns false (message given in called function);
11803  SplitRepeat returns false (message given in called function).
11804 */
11805 {
11806  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ProcessOneTimetableLine," + AnsiString(Count) + "," + OneLine + "," +
11807  AnsiString((short)FinalCall) + "," + AnsiString((short)CheckLocationsExistInRailway));
11808  TTrainDataEntry TempTrainDataEntry;
11809 
11810  EndOfFile = false;
11811  StripSpaces(0, OneLine);
11812  // strip both leading and trailing spaces at ends of line and spaces before and after all commas and
11813  // semicolons within the line
11814  ServiceReference = "";
11815  if(OneLine != "")
11816  {
11817  if(OneLine[1] != '*')
11818  {
11819  int DelimPos = OneLine.Pos(';');
11820  int CPos = OneLine.Pos(','); //added at v2.21.0 as Sns entries without a description have comma instead of semicolon but service ref. still valid
11821  if((CPos > 0) && (CPos < DelimPos))
11822  {
11823  DelimPos = CPos;
11824  }
11825 
11826  if(DelimPos == 0)
11827  {
11828  ServiceReference = OneLine.SubString(1, 8);
11829  }
11830  else
11831  {
11832  ServiceReference = OneLine.SubString(1, (DelimPos - 1));
11833  }
11834  }
11835  }
11836  bool AllCommas = true;
11837 
11838  for(int x = 1; x < OneLine.Length() + 1; x++) // check for nothing but commas (may be all commas if created from Excel) or a blank line
11839  {
11840  if(OneLine[x] != ',')
11841  {
11842  AllCommas = false;
11843  }
11844  }
11845  if(AllCommas || (OneLine == ""))
11846  {
11847  if(Count > 0)
11848  {
11849  EndOfFile = true;
11850  // returns true for a blank line (or a line of all commas) - treated as end of file
11851  Utilities->CallLogPop(1018);
11852  return(true);
11853  }
11854  else // count == 0 so not yet found a start time, no message to be given
11855  {
11856  Utilities->CallLogPop(754);
11857  return(false);
11858  }
11859  }
11860  AnsiString First = "", Second = "", Third = "", Fourth = "";
11861  int RearStartOrRepeatMins = 0, FrontStartOrRepeatDigits = 0, NumberOfRepeats = 0;
11862  TDateTime EventTime(0), ArrivalTime(0), DepartureTime(0);
11863  TDateTime StartTime(0);
11864  TNumList ExitList;
11865  bool Warning = false;
11866 
11867  if(Count == 0) // no start time found yet
11868  {
11869 /* dropped at v0.6b
11870  AnyHeadCodeValid = false;
11871  if(OneLine.SubString(6,5) == ";0000")
11872  {
11873  AnyHeadCodeValid = true;
11874  }
11875 */
11876  if(!CheckTimeValidity(0, OneLine, StartTime))
11877  {
11878  // no message is given for an invalid time as it's assumed to be an irrelevant line
11879  // if no start time is found at all then an error message is given in the calling function
11880  // AnyHeadCodeValid = false;
11881  Utilities->CallLogPop(755);
11882  return(false);
11883  }
11884  if(FinalCall) // here if start time valid
11885  {
11886  TTClockTime = StartTime;
11887  TimetableStartTime = StartTime;
11888  }
11889  }
11890  else
11891  {
11892  AnsiString TrainInfoStr = "", HeadCode = "", Description = "";
11893  int StartSpeed = 0, MaxRunningSpeed = 0, Mass = 0;
11894  double MaxBrakeRate = 0;
11895  double PowerAtRail = 0;
11896  int SignallerSpeed = 0;
11897  if(OneLine[1] == '*')
11898  {
11899  Utilities->CallLogPop(1581);
11900  return(true);
11901  // ignore any line beginning with '*' but return true as there is no error
11902  }
11903  int Pos = OneLine.Pos(',');
11904  if(Pos == 0)
11905  {
11906  int SubStringLength = 20;
11907  if(OneLine.Length() < 20)
11908  {
11909  SubStringLength = OneLine.Length();
11910  }
11911  TimetableMessage(GiveMessages, "Error in timetable - entry incomplete: see '" + OneLine.SubString(1, SubStringLength) + "'....");
11912  Utilities->CallLogPop(766);
11913  return(false);
11914  }
11915  TrainInfoStr = OneLine.SubString(1, Pos - 1);
11916  if(!SplitTrainInfo(0, TrainInfoStr, HeadCode, Description, StartSpeed, MaxRunningSpeed, Mass, MaxBrakeRate, PowerAtRail, SignallerSpeed,
11917  GiveMessages)) // error messages given in SplitTrainInfo
11918  {
11919  Utilities->CallLogPop(773);
11920  return(false);
11921  }
11922  if(FinalCall)
11923  {
11924  // store Train info - conversions done in SplitTrainInfo
11925  // only headcode mandatory for continuing services
11926  //HeadCode = ServiceReference until final section of SecondPassActions
11927  TempTrainDataEntry.HeadCode = HeadCode;
11928  TempTrainDataEntry.ServiceReference = HeadCode;
11929  TempTrainDataEntry.FixedDescription = Description; //name changed at v2.16.1
11930  TempTrainDataEntry.ExplicitDescription = false;
11931  if(Description != "")
11932  {
11933  TempTrainDataEntry.ExplicitDescription = true;
11934  }
11935  TempTrainDataEntry.StartSpeed = StartSpeed;
11936  TempTrainDataEntry.Mass = Mass;
11937  TempTrainDataEntry.MaxRunningSpeed = MaxRunningSpeed;
11938  TempTrainDataEntry.MaxBrakeRate = MaxBrakeRate;
11939  TempTrainDataEntry.PowerAtRail = PowerAtRail;
11940  TempTrainDataEntry.SignallerSpeed = SignallerSpeed;
11941  TTrainOperatingData TempTrainOperatingData;
11942  TempTrainDataEntry.TrainOperatingDataVector.push_back(TempTrainOperatingData); // push empty vector for now
11943  }
11944  AnsiString NewRemainder = OneLine.SubString(Pos + 1, OneLine.Length() - Pos);
11945  // now left with series of entries for this train, but there may be a string of commas at the end of the line if created by Excel
11946  // so strip them off
11947  while(NewRemainder[NewRemainder.Length()] == ',')
11948  {
11949  if(NewRemainder.Length() > 1)
11950  {
11951  NewRemainder = NewRemainder.SubString(1, NewRemainder.Length() - 1);
11952  }
11953  else
11954  {
11955  NewRemainder = "";
11956  break;
11957  }
11958  }
11959  // check if zero length & fail if so
11960  if(NewRemainder == "")
11961  {
11962  TimetableMessage(GiveMessages, "Error in timetable - no events following train: '" + OneLine + "'");
11963  Utilities->CallLogPop(769);
11964  return(false);
11965  }
11966  // now have one more entry than there are commas
11967  int CommaCount = 0;
11968  for(int x = 1; x < NewRemainder.Length() + 1; x++)
11969  {
11970  if(NewRemainder[x] == ',')
11971  {
11972  CommaCount++;
11973  }
11974  } // must have at least 1 comma, for start & finish entries, unless train is entered under signaller control
11975  if(CommaCount == 0)
11976  {
11977  if((NewRemainder.SubString(7, 3) != "Snt") || (NewRemainder[NewRemainder.Length()] != 'S'))
11978  {
11979  int SubStringLength = 20;
11980  if(OneLine.Length() < 20)
11981  {
11982  SubStringLength = OneLine.Length();
11983  }
11984  TimetableMessage(GiveMessages,
11985  "Error in timetable - must have at least a start and a finish event for a train that is not started under signaller control - see line beginning: '" +
11986  OneLine.SubString(1, SubStringLength) + "'....");
11987  Utilities->CallLogPop(783);
11988  return(false);
11989  }
11990  }
11991  AnsiString OneEntry = "";
11992  TTimetableFormatType FormatType;
11993  TTimetableSequenceType SequenceType;
11994  TTimetableLocationType LocationType;
11995  TTimetableShuttleLinkType ShuttleLinkType;
11996  bool FinishFlag = false;
11997 // bool NewTrain = false;//added at v2.14.0 to record created trains for later zero power checks <-- not needed after zero power restriction dropped
11998  for(int x = 0; x < CommaCount + 1; x++)
11999  {
12000  if((CommaCount == 0) || (x < CommaCount))
12001  // i.e. train entered under signaller control with no repeats, or entry is not the last,
12002  // in which case there's a comma & finish element or repeat still to come this entry could
12003  // be a finish but can't be a repeat
12004  {
12005  if(CommaCount == 0)
12006  {
12007  OneEntry = NewRemainder;
12008  NewRemainder = "";
12009  }
12010  else
12011  {
12012  Pos = NewRemainder.Pos(',');
12013  OneEntry = NewRemainder.SubString(1, Pos - 1);
12014  NewRemainder = NewRemainder.SubString(Pos + 1, NewRemainder.Length() - Pos);
12015  }
12016  First = "";
12017  Second = "";
12018  Third = "";
12019  Fourth = "";
12020  RearStartOrRepeatMins = 0;
12021  FrontStartOrRepeatDigits = 0;
12022  NumberOfRepeats = 0;
12023  if(!SplitEntry(0, OneEntry, GiveMessages, CheckLocationsExistInRailway, First, Second, Third, Fourth, RearStartOrRepeatMins,
12024  FrontStartOrRepeatDigits, FormatType, LocationType, SequenceType, ShuttleLinkType, ExitList, Warning))
12025  {
12026  TimetableMessage(GiveMessages, "Error in timetable - Event: '" + OneEntry + "'");
12027  Utilities->CallLogPop(756);
12028  return(false);
12029  }
12030 /* not needed at v2.19.0
12031  if((Second == "Snt") || (Second == "Snt-sh")) //added at v2.14.0, see above
12032  {
12033  NewTrain = true; not needed when zero power restrictions removed
12034  }
12035 */
12036  // check if warning for Frh or Fjo & reject
12037  if(Warning && (Second == "Frh"))
12038  {
12039  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry + "': warnings cannot be given for 'Frh' events");
12040  Utilities->CallLogPop(1793);
12041  return(false);
12042  }
12043  if(Warning && (Second == "Fjo"))
12044  {
12045  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
12046  "': warnings cannot be given for 'Fjo' events, for a train join warning add a 'W' prefix to the 'jbo' event");
12047  Utilities->CallLogPop(1794);
12048  return(false);
12049  }
12050  //below added at v2.14.0 to prevent unpowered trains attempting to be joined by (Second == jbo), split (Second -- fsp or rsp),
12051  //or change direction. Form a new service dealt with below for zero power as it's a finish event. <-- removed at v2.19.0
12052 
12053 /* restriction removed at v2.19.0
12054  if(NewTrain && (PowerAtRail < 1) && (Second == "jbo"))
12055  {
12056  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
12057  "': a train created without power can't 'be joined by' another train (i.e. can't include command 'jbo'), "
12058  "use command 'Fjo' (i.e. 'join' another train) instead immediately after the line containing 'Snt', and use "
12059  "command 'jbo' for the train it is to join.");
12060  Utilities->CallLogPop(2545);
12061  return(false);
12062  }
12063  if(NewTrain && (PowerAtRail < 1) && ((Second == "fsp") || (Second == "rsp")))
12064  {
12065  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
12066  "': a train created without power can't split.");
12067  Utilities->CallLogPop(2546);
12068  return(false);
12069  }
12070  if(NewTrain && (PowerAtRail < 1) && (Second == "cdt"))
12071  {
12072  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
12073  "': a train created without power can't change direction under timetable control.");
12074  Utilities->CallLogPop(2547);
12075  return(false);
12076  }
12077 */
12078  //end of new additions
12079  if(x == 0) // should be start event
12080  {
12081  if(SequenceType != StartSequence)
12082  {
12083  TimetableMessage(GiveMessages, "Error in timetable - First event not a start event: '" + OneEntry + "'");
12084  Utilities->CallLogPop(784);
12085  return(false);
12086  }
12087  if((Second == "Snt") && (Fourth == 'S') && (NewRemainder != ""))
12088  {
12089  if(NewRemainder[1] != 'R')
12090  {
12091  TimetableMessage(GiveMessages,
12092  "Error in timetable - the only event that can follow a train created under signaller control is a repeat, see '" +
12093  OneEntry + "'");
12094  Utilities->CallLogPop(787);
12095  return(false);
12096  }
12097  }
12098  if((Second == "Snt") || (Second == "Snt-sh"))
12099  // need full train information including non-default values for at least HeadCode, Description,
12100  // MaxRunningSpeed, Mass, MaxBrakeRate, & PowerAtRail
12101  {
12102  if((HeadCode == "") || (Description == "") || (MaxRunningSpeed == 0) || (Mass == 0) || (MaxBrakeRate == 0)) // ||
12103  // (PowerAtRail == 0)) allowed 0 for power at v2.4.0
12104  {
12105  TimetableMessage(GiveMessages, "Error in timetable - train information incomplete before 'Snt' or 'Snt-sh' start event: '" +
12106  OneEntry + "'");
12107  Utilities->CallLogPop(1783);
12108  return(false);
12109  }
12110  }
12111  if((Second == "Sfs") || (Second == "Sns") || (Second == "Sns-sh") || (Second == "Sns-fsh"))
12112  // service continuation - need at least non-default value for HeadCode
12113  {
12114  if(HeadCode == "")
12115  {
12116  TimetableMessage(GiveMessages, "Error in timetable - headcode missing before 'Sfs', 'Sns', 'Sns-sh' or 'Sns-fsh' start event: '" +
12117  OneEntry + "'");
12118  Utilities->CallLogPop(788);
12119  return(false);
12120  }
12121  if((StartSpeed != 0) || (MaxRunningSpeed != 0) || (Mass != 0) || (MaxBrakeRate != 0) || (PowerAtRail != 0))
12122  {
12123  TimetableMessage(GiveMessages,
12124  "Error in timetable - information additional to a headcode & optional description given before 'Sfs', 'Sns', 'Sns-sh' or 'Sns-fsh' start event: '" +
12125  OneEntry + "'");
12126  Utilities->CallLogPop(843);
12127  return(false);
12128  }
12129  }
12130  }
12131  if(SequenceType == FinishSequence)
12132  {
12133  FinishFlag = true;
12134  // marker for only permitted additional entry being a repeat, only needed if the
12135  // finish entry is not the last entry
12136  }
12137  if(FinalCall)
12138  {
12139  // interpret & add to ActionVector
12140  TDateTime TempTime;
12141  TActionVectorEntry ActionVectorEntry;
12142  ActionVectorEntry.FormatType = FormatType;
12143  ActionVectorEntry.LocationType = LocationType;
12144  ActionVectorEntry.SequenceType = SequenceType;
12145  ActionVectorEntry.ShuttleLinkType = ShuttleLinkType;
12146  ActionVectorEntry.Warning = Warning;
12147  if(FormatType == TimeLoc)
12148  {
12149  if(CheckTimeValidity(1, First, ActionVectorEntry.EventTime))
12150  {
12151  ;
12152  } // these will all be true as final call
12153  ActionVectorEntry.LocationName = Second;
12154  if(Third == "") //added at v2.23.0
12155  {
12156  ActionVectorEntry.MinDwellTime = 30.0;
12157  }
12158  else
12159  {
12160  ActionVectorEntry.MinDwellTime = Third.ToDouble();
12161  }
12162  }
12163  else if(FormatType == PassTime) // new
12164  {
12165  if(CheckTimeValidity(17, First, ActionVectorEntry.EventTime))
12166  {
12167  ;
12168  }
12169  ActionVectorEntry.Command = Second;
12170  ActionVectorEntry.LocationName = Third;
12171  }
12172  else if(FormatType == TimeTimeLoc)
12173  {
12174  if(CheckTimeValidity(2, First, ActionVectorEntry.ArrivalTime))
12175  {
12176  ;
12177  }
12178  if(CheckTimeValidity(3, Second, ActionVectorEntry.DepartureTime))
12179  {
12180  ;
12181  }
12182  ActionVectorEntry.LocationName = Third;
12183  if(Fourth == "") //added at v2.23.0
12184  {
12185  ActionVectorEntry.MinDwellTime = 30.0;
12186  }
12187  else
12188  {
12189  ActionVectorEntry.MinDwellTime = Fourth.ToDouble();
12190  }
12191  }
12192  else if(FormatType == TimeCmd)
12193  {
12194  if(CheckTimeValidity(4, First, ActionVectorEntry.EventTime))
12195  {
12196  ;
12197  }
12198  ActionVectorEntry.Command = Second;
12199  }
12200  else if(FormatType == ExitRailway)
12201  {
12202  if(CheckTimeValidity(18, First, ActionVectorEntry.EventTime))
12203  {
12204  ;
12205  }
12206  ActionVectorEntry.Command = Second;
12207  ActionVectorEntry.ExitList = ExitList;
12208  }
12209  else if(FormatType == StartNew)
12210  {
12211  if(CheckTimeValidity(5, First, ActionVectorEntry.EventTime))
12212  {
12213  ;
12214  }
12215  ActionVectorEntry.Command = Second;
12216  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
12217  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
12218  if(Fourth == 'S')
12219  {
12220  ActionVectorEntry.SignallerControl = true;
12221  }
12222  }
12223  else if(FormatType == SNTShuttle)
12224  {
12225  if(CheckTimeValidity(6, First, ActionVectorEntry.EventTime))
12226  {
12227  ;
12228  }
12229  ActionVectorEntry.Command = Second;
12230  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
12231  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
12232  ActionVectorEntry.OtherHeadCode = Fourth;
12233  }
12234  else if(FormatType == SNSShuttle)
12235  {
12236  if(CheckTimeValidity(7, First, ActionVectorEntry.EventTime))
12237  {
12238  ;
12239  }
12240  ActionVectorEntry.Command = Second;
12241  ActionVectorEntry.OtherHeadCode = Third;
12242  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
12243  }
12244  else if(FormatType == TimeCmdHeadCode) //fsp/rsp is TimeCmdHeadCode but may have a Fourth - see below
12245  {
12246  if(CheckTimeValidity(8, First, ActionVectorEntry.EventTime))
12247  {
12248  ;
12249  }
12250  ActionVectorEntry.Command = Second;
12251  ActionVectorEntry.OtherHeadCode = Third;
12252  if((Second == "fsp") || (Second == "rsp")) //new at v2.15.0 & checked in SplitEntry
12253  {
12254  ActionVectorEntry.SplitDistribution = "50-50"; //added at v2.18.0
12255  if(Fourth != "")
12256  {
12257  ActionVectorEntry.SplitDistribution = Fourth;
12258  }
12259  }
12260  }
12261  else if((FormatType == FNSNonRepeatToShuttle) || (FormatType == SNSNonRepeatFromShuttle))
12262  {
12263  if(CheckTimeValidity(9, First, ActionVectorEntry.EventTime))
12264  {
12265  ;
12266  }
12267  ActionVectorEntry.Command = Second;
12268  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Third;
12269  }
12270  else if(FormatType == FSHNewService)
12271  {
12272  if(CheckTimeValidity(10, First, ActionVectorEntry.EventTime))
12273  {
12274  ;
12275  }
12276  ActionVectorEntry.Command = Second;
12277  ActionVectorEntry.OtherHeadCode = Third;
12278  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
12279  }
12280  else if(FormatType == FinRemHere)
12281  {
12282  ActionVectorEntry.Command = Second;
12283  }
12284  else if(FormatType == TimeCmdDescription) //new at v2.15.0
12285  {
12286  if(CheckTimeValidity(35, First, ActionVectorEntry.EventTime))
12287  {
12288  ;
12289  }
12290  ActionVectorEntry.Command = Second;
12291  ActionVectorEntry.NewDescription = Third;
12292  }
12293  else if(FormatType == TimeCmdMaxSpeed) //new at v2.21.0
12294  {
12295  if(CheckTimeValidity(45, First, ActionVectorEntry.EventTime))
12296  {
12297  ;
12298  }
12299  ActionVectorEntry.Command = Second;
12300  ActionVectorEntry.NewMaxSpeed = Third;
12301  }
12302  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
12303  }
12304  }
12305  else // last entry, & not entered under signaller control with no repeats, i.e. could be finish or repeat
12306  {
12307  OneEntry = NewRemainder;
12308  First = "";
12309  Second = "";
12310  Third = "";
12311  Fourth = "";
12312  RearStartOrRepeatMins = 0;
12313  FrontStartOrRepeatDigits = 0;
12314  NumberOfRepeats = 0;
12315  if((FinishFlag) && (OneEntry[1] != 'R'))
12316  // already had a finish entry
12317  {
12318  TimetableMessage(GiveMessages, "Error in timetable - Last event = '" + OneEntry + "'. An earlier finish event has been found with something other than a repeat following it - only a repeat can follow a finish event.");
12319  Utilities->CallLogPop(79);
12320  return(false);
12321  }
12322  if(OneEntry[1] != 'R') // must be finish
12323  {
12324  if(!SplitEntry(1, OneEntry, GiveMessages, CheckLocationsExistInRailway, First, Second, Third, Fourth, RearStartOrRepeatMins,
12325  FrontStartOrRepeatDigits, FormatType, LocationType, SequenceType, ShuttleLinkType, ExitList, Warning))
12326  {
12327  TimetableMessage(GiveMessages, "Error in timetable - Event: '" + OneEntry + "'.\nIf the program version is not the latest the "
12328  "timetable may have features that aren't compatible with the version in use.");
12329  Utilities->CallLogPop(757);
12330  return(false);
12331  }
12332  //below added at v2.14.0 to prevent unpowered trains attempting to form a new service.
12333 /* restriction removed at v2.19.0
12334  if(NewTrain && (PowerAtRail < 1) && ((Second == "Fns") || (Second == "Frh-sh") || (Second == "Fns-sh") || (Second == "F-nshs")))
12335  {
12336  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
12337  "': a train created without power can't form a new service.");
12338  Utilities->CallLogPop(2548);
12339  return(false);
12340  }
12341  //end of new additions
12342 */
12343  if(SequenceType != FinishSequence)
12344  {
12345  TimetableMessage(GiveMessages, "Error in timetable - last event should be a finish: '" + OneEntry + "'");
12346  Utilities->CallLogPop(785);
12347  return(false);
12348  }
12349  if(FinalCall)
12350  {
12351  // interpret & add to ActionVector
12352  TDateTime TempTime;
12353  TActionVectorEntry ActionVectorEntry;
12354  ActionVectorEntry.FormatType = FormatType;
12355  ActionVectorEntry.LocationType = LocationType;
12356  ActionVectorEntry.SequenceType = SequenceType;
12357  ActionVectorEntry.ShuttleLinkType = ShuttleLinkType;
12358  ActionVectorEntry.Warning = Warning;
12359  if(FormatType == TimeCmd)
12360  {
12361  if(CheckTimeValidity(11, First, ActionVectorEntry.EventTime))
12362  {
12363  ;
12364  }
12365  ActionVectorEntry.Command = Second;
12366  }
12367  else if(FormatType == TimeCmdHeadCode)
12368  {
12369  if(CheckTimeValidity(12, First, ActionVectorEntry.EventTime))
12370  {
12371  ;
12372  }
12373  ActionVectorEntry.Command = Second;
12374  ActionVectorEntry.OtherHeadCode = Third;
12375  }
12376  else if(FormatType == FNSNonRepeatToShuttle)
12377  {
12378  if(CheckTimeValidity(13, First, ActionVectorEntry.EventTime))
12379  {
12380  ;
12381  }
12382  ActionVectorEntry.Command = Second;
12383  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Third;
12384  }
12385  else if(FormatType == FSHNewService)
12386  {
12387  if(CheckTimeValidity(14, First, ActionVectorEntry.EventTime))
12388  {
12389  ;
12390  }
12391  ActionVectorEntry.Command = Second;
12392  ActionVectorEntry.OtherHeadCode = Third;
12393  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
12394  }
12395  else if(FormatType == ExitRailway)
12396  {
12397  if(CheckTimeValidity(19, First, ActionVectorEntry.EventTime))
12398  {
12399  ;
12400  }
12401  ActionVectorEntry.Command = Second;
12402  ActionVectorEntry.ExitList = ExitList;
12403  }
12404  else if(FormatType == FinRemHere)
12405  {
12406  ActionVectorEntry.Command = Second;
12407  }
12408  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
12409  }
12410  }
12411  else // repeat
12412  {
12413  if(!SplitRepeat(0, OneEntry, RearStartOrRepeatMins, FrontStartOrRepeatDigits, NumberOfRepeats, GiveMessages))
12414  {
12415  Utilities->CallLogPop(786);
12416  // error messages given in SplitRepeat
12417  return(false);
12418  }
12419  if(FinalCall)
12420  {
12421  TActionVectorEntry ActionVectorEntry;
12422  ActionVectorEntry.FormatType = Repeat;
12423  ActionVectorEntry.LocationType = LocTypeForRepeatEntry;
12424  ActionVectorEntry.SequenceType = SequTypeForRepeatEntry;
12425  ActionVectorEntry.ShuttleLinkType = ShuttleLinkTypeForRepeatEntry;
12426  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
12427  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
12428  ActionVectorEntry.NumberOfRepeats = NumberOfRepeats;
12429  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
12430  }
12431  }
12432  }
12433  }
12434  if(FinalCall)
12435  {
12436  TrainDataVector.push_back(TempTrainDataEntry);
12437  }
12438  }
12439  Utilities->CallLogPop(80);
12440  return(true);
12441 }
12442 
12443 // ---------------------------------------------------------------------------
12444 
12445 bool TTrainController::Last2CharactersBothDigits(int Caller, AnsiString HeadCode)
12446 {
12447  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",Last2CharactersBothDigits," + HeadCode);
12448  if((HeadCode[HeadCode.Length() - 1] < '0') || (HeadCode[HeadCode.Length() - 1] > '9'))
12449  {
12450  Utilities->CallLogPop(1890);
12451  return(false);
12452  }
12453  if((HeadCode[HeadCode.Length()] < '0') || (HeadCode[HeadCode.Length()] > '9'))
12454  {
12455  Utilities->CallLogPop(1891);
12456  return(false);
12457  }
12458  Utilities->CallLogPop(1892);
12459  return(true);
12460 }
12461 
12462 // ---------------------------------------------------------------------------
12463 
12464 bool TTrainController::CheckTimeValidity(int Caller, AnsiString TimeStr, TDateTime &Time)
12465 // 1st 5 chars must be HH:MM, anything else will be ignored
12466 {
12467  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckTimeValidity," + TimeStr);
12468  if(TimeStr.Length() < 5)
12469  {
12470  Utilities->CallLogPop(926);
12471  return(false);
12472  }
12473  if((TimeStr[1] < '0') || (TimeStr[1] > '9'))
12474  {
12475  Utilities->CallLogPop(927);
12476  return(false);
12477  }
12478  if((TimeStr[2] < '0') || (TimeStr[2] > '9'))
12479  {
12480  Utilities->CallLogPop(928);
12481  return(false);
12482  }
12483  if(TimeStr[3] != ':')
12484  {
12485  Utilities->CallLogPop(929);
12486  return(false);
12487  }
12488  if((TimeStr[4] < '0') || (TimeStr[4] > '5'))
12489  {
12490  Utilities->CallLogPop(930);
12491  return(false);
12492  }
12493  if((TimeStr[5] < '0') || (TimeStr[5] > '9'))
12494  {
12495  Utilities->CallLogPop(931);
12496  return(false);
12497  }
12498  while(TimeStr.Length() > 5)
12499  {
12500  TimeStr = TimeStr.SubString(1, TimeStr.Length() - 1);
12501  }
12502  double WholeHours = (AnsiString(TimeStr[1]) + AnsiString(TimeStr[2])).ToDouble();
12503  double FracHour = ((AnsiString(TimeStr[4]) + AnsiString(TimeStr[5])).ToDouble()) / 60.0;
12504 
12505  if((WholeHours + FracHour) >= 95.98334)
12506  {
12507  Utilities->CallLogPop(1817);
12508  return(false); // > 95h 59m
12509  }
12510  Time = TDateTime((WholeHours + FracHour) / 24);
12511  Utilities->CallLogPop(932);
12512  return(true);
12513 }
12514 
12515 // ---------------------------------------------------------------------------
12516 
12517 bool TTrainController::SplitEntry(int Caller, AnsiString OneEntry, bool GiveMessages, bool CheckLocationsExistInRailway, AnsiString &First, AnsiString &Second,
12518  AnsiString &Third, AnsiString &Fourth, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, TTimetableFormatType &FormatType,
12519  TTimetableLocationType &LocationType, TTimetableSequenceType &SequenceType, TTimetableShuttleLinkType &ShuttleLinkType, TNumList &ExitList, bool &Warning)
12520 /* This is a train action entry from a single line of the timetable, i.e. not train information and not a repeat entry.
12521  Return false for failure.
12522  See description above under ProcessOneTimetableLinefor details of train action entries
12523  NB all types set except LocationType for Snt as may be located or not
12524 */{
12525  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitEntry," + OneEntry);
12526  Warning = false;
12527  TDateTime TempTime;
12528 
12529  if(OneEntry.Length() > 0)
12530  {
12531  if(OneEntry[1] == 'W') // warning
12532  {
12533  Warning = true;
12534  OneEntry = OneEntry.SubString(2, OneEntry.Length() - 1);
12535  // strip it off
12536  }
12537  }
12538  if(OneEntry == "Frh")
12539  {
12540  FormatType = FinRemHere;
12541  SequenceType = FinishSequence;
12542  LocationType = AtLocation;
12543  ShuttleLinkType = NotAShuttleLink;
12544  Second = "Frh";
12545  Utilities->CallLogPop(1016);
12546  return(true);
12547  }
12548  if(OneEntry.Length() < 7)
12549  {
12550  Utilities->CallLogPop(907);
12551  return(false); // 'HH:MM;' + at least a one-letter location name
12552  }
12553  int Pos = OneEntry.Pos(';'); // first segment delimiter
12554 
12555  if(Pos != 6)
12556  {
12557  Utilities->CallLogPop(908);
12558  return(false);
12559  // no delimiter or delimiter not in position 6, has to be a time so fail
12560  }
12561  First = OneEntry.SubString(1, 5); // has to be a time
12562  if(!CheckTimeValidity(16, First, TempTime))
12563  {
12564  Utilities->CallLogPop(909);
12565  return(false);
12566  }
12567  AnsiString Remainder = OneEntry.SubString(Pos + 1, OneEntry.Length() - Pos);
12568 
12569 // if((Remainder[1] >= '0') && (Remainder[1] <= '9')) changed at v2.16.0 so only 'digit-digit-colon....' interpreted as a time - to allow locations to begin with digits
12570  if((Remainder.Length() >= 3) && (Remainder[1] >= '0') && (Remainder[1] <= '9') && (Remainder[2] >= '0') && (Remainder[2] <= '9') && (Remainder[3] == ':'))
12571  // next segment is a time so this is a TimeTimeLoc & 3rd seg has to be a location to be valid
12572  {
12573  if(Remainder.Length() < 7)
12574  {
12575  Utilities->CallLogPop(910);
12576  return(false); // 'HH:MM;' + at least a one-letter location name
12577  }
12578  Pos = Remainder.Pos(';'); // second segment delimiter
12579  if(Pos == 0)
12580  {
12581  Utilities->CallLogPop(911);
12582  return(false);
12583  // no delimiter, has to be one between departure time & location
12584  }
12585  Second = Remainder.SubString(1, 5); // has to be a time
12586  if(!CheckTimeValidity(15, Second, TempTime))
12587  {
12588  Utilities->CallLogPop(912);
12589  return(false);
12590  }
12591  Third = Remainder.SubString(Pos + 1, Remainder.Length() - Pos); //may be a fourth = non-default dwell time
12592  Pos = Third.Pos(';');
12593  if(Pos != 0) //there is a fourth
12594  {
12595  Fourth = Third.SubString(Pos + 1, Remainder.Length() - Pos); //min dwell time
12596  if(Fourth == "")
12597  {
12598  Utilities->CallLogPop(2731);
12599  return(false);
12600  }
12601  Third = Third.SubString(1, Pos - 1); //location
12602  for(int x = 1; x <= Fourth.Length(); x++)
12603  {
12604  if((Fourth[x] < '0') || (Fourth[x] > '9'))
12605  {
12606  TimetableMessage(GiveMessages, "Invalid character in minimum dwell time in " + OneEntry + ". Must be a whole number of seconds.");
12607  Utilities->CallLogPop(2732);
12608  return(false);
12609  }
12610  }
12611  if(Fourth.ToDouble() < 29.9)
12612  {
12613  TimetableMessage(GiveMessages, "Error in timetable - a minimum dwell time can't be less than 30 seconds: '" + OneEntry + "'");
12614  Utilities->CallLogPop(2733);
12615  return(false);
12616  }
12617  }
12618  if(!CheckLocationValidity(0, Third, GiveMessages, CheckLocationsExistInRailway))
12619  {
12620  Utilities->CallLogPop(913);
12621  return(false);
12622  }
12623  FormatType = TimeTimeLoc;
12624  SequenceType = IntermediateSequence;
12625  LocationType = AtLocation;
12626  ShuttleLinkType = NotAShuttleLink;
12627  Utilities->CallLogPop(914);
12628  return(true);
12629  }
12630  Pos = Remainder.Pos(';'); // second segment delimiter
12631  if(Pos == 0) // no third segment so second must be a location, or cdt
12632  {
12633  Second = Remainder;
12634  if(Second == "cdt")
12635  {
12636  FormatType = TimeCmd;
12637  ShuttleLinkType = NotAShuttleLink;
12638  LocationType = AtLocation;
12639  SequenceType = IntermediateSequence;
12640  Utilities->CallLogPop(915);
12641  return(true);
12642  }
12643  if(!CheckLocationValidity(1, Second, GiveMessages, CheckLocationsExistInRailway))
12644  {
12645  Utilities->CallLogPop(916);
12646  return(false);
12647  }
12648  else
12649  {
12650  FormatType = TimeLoc; //TimeLoc with default dwell time
12651  LocationType = AtLocation;
12652  SequenceType = IntermediateSequence;
12653  ShuttleLinkType = NotAShuttleLink;
12654  Utilities->CallLogPop(917);
12655  return(true);
12656  }
12657  }
12658 //check here for TimeLoc with non-default dwell time
12659  if((Pos != 0) && (CheckLocationValidity(4, Remainder.SubString(1, Pos - 1), false, CheckLocationsExistInRailway))) //false for no give messages as likely to be ok
12660  {
12661  Second = Remainder.SubString(1, Pos - 1); //location
12662  if(NotACommand(0, Second)) //added at v2.23.0 so invert works as that permitted without a railway loaded so any text (including commands) can count as locations
12663  {
12664  Third = Remainder.SubString(Pos + 1, Remainder.Length() - Pos); //non-default dwell time
12665  if(Third == "")
12666  {
12667  Utilities->CallLogPop(2734);
12668  return(false);
12669  }
12670  for(int x = 1; x <= Third.Length(); x++)
12671  {
12672  if((Third[x] < '0') || (Third[x] > '9'))
12673  {
12674  TimetableMessage(GiveMessages, "Invalid character in minimum dwell time in " + OneEntry + ". Must be a whole number of seconds.");
12675  Utilities->CallLogPop(2735);
12676  return(false);
12677  }
12678  }
12679  if(Third.ToDouble() < 29.9)
12680  {
12681  TimetableMessage(GiveMessages, "Error in timetable - a minimum dwell time can't be less than 30 seconds: '" + OneEntry + "'");
12682  Utilities->CallLogPop(2736);
12683  return(false);
12684  }
12685  FormatType = TimeLoc; //TimeLoc with default dwell time
12686  LocationType = AtLocation;
12687  SequenceType = IntermediateSequence;
12688  ShuttleLinkType = NotAShuttleLink;
12689  Utilities->CallLogPop(2737);
12690  return(true);
12691  }
12692  }
12693  // here if second segment is a command, with a third & maybe fourth segments
12694  if((Pos != 4) && (Pos != 7) && (Pos != 8))
12695  {
12696  Utilities->CallLogPop(918);
12697  return(false);
12698  // no third segement or not in position 4 or 7, & should be since all commands are 3, 6 or 7 letters
12699  }
12700  Second = Remainder.SubString(1, Pos - 1); // command
12701 
12702  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12703  // details
12704  Pos = Remainder.Pos(';'); // third segment delimiter
12705  if(Pos == 0)
12706  {
12707  Third = Remainder;
12708  }
12709  else
12710  {
12711  Third = Remainder.SubString(1, Pos - 1);
12712  Fourth = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12713  }
12714 
12715  if((Second == "Snt") || (Second == "Snt-sh"))
12716  // third has to be 2 element idents with a space between
12717  {
12718  int SpacePos = Third.Pos(' ');
12719  if(SpacePos == 0)
12720  {
12721  Utilities->CallLogPop(919);
12722  return(false); // no space
12723  }
12724  AnsiString RearStartStr = Third.SubString(1, SpacePos - 1);
12725  AnsiString FrontStartStr = Third.SubString(SpacePos + 1, Third.Length() - SpacePos);
12726  // int RearPosition=0, FrontPosition=0, RearExitPos=0;
12727  if(CheckLocationsExistInRailway)
12728  {
12729  if(!CheckStartPositionValidity(0, RearStartStr, FrontStartStr, GiveMessages))
12730  {
12731  Utilities->CallLogPop(920);
12732  return(false);
12733  }
12734  RearStartOrRepeatMins = Track->GetTrackVectorPositionFromString(3, RearStartStr, GiveMessages);
12735  FrontStartOrRepeatDigits = Track->GetTrackVectorPositionFromString(4, FrontStartStr, GiveMessages);
12736  }
12737  if(Second == "Snt")
12738  {
12739  FormatType = StartNew;
12740  SequenceType = StartSequence;
12741  LocationType = NoLocation;
12742  // can't be set until know whether located or not - done in SecondPassActions
12743  ShuttleLinkType = NotAShuttleLink;
12744  }
12745  else // Snt-sh
12746  {
12747  FormatType = SNTShuttle;
12748  LocationType = AtLocation;
12749  SequenceType = StartSequence;
12750  ShuttleLinkType = ShuttleLink;
12751  if(!CheckHeadCodeValidity(0, GiveMessages, Fourth))
12752  {
12753  Utilities->CallLogPop(1038);
12754  return(false);
12755  }
12756  }
12757  Utilities->CallLogPop(921);
12758  return(true);
12759  }
12760  if(Second == "Sns-sh") // third & fourth have to be headcodes
12761  {
12762  FormatType = SNSShuttle;
12763  LocationType = AtLocation;
12764  SequenceType = StartSequence;
12765  ShuttleLinkType = ShuttleLink;
12766  if(!CheckHeadCodeValidity(1, GiveMessages, Third))
12767  {
12768  Utilities->CallLogPop(1039);
12769  return(false);
12770  }
12771  if(!CheckHeadCodeValidity(2, GiveMessages, Fourth))
12772  {
12773  Utilities->CallLogPop(1040);
12774  return(false);
12775  }
12776  Utilities->CallLogPop(1041);
12777  return(true);
12778  }
12779  if(Second == "F-nshs")
12780  {
12781  FormatType = FNSNonRepeatToShuttle;
12782  LocationType = AtLocation;
12783  SequenceType = FinishSequence;
12784  ShuttleLinkType = ShuttleLink;
12785  if(!CheckHeadCodeValidity(3, GiveMessages, Third))
12786  {
12787  Utilities->CallLogPop(1047);
12788  return(false);
12789  }
12790  Utilities->CallLogPop(1048);
12791  return(true);
12792  }
12793  if(Second == "Sns-fsh")
12794  {
12795  FormatType = SNSNonRepeatFromShuttle;
12796  LocationType = AtLocation;
12797  SequenceType = StartSequence;
12798  ShuttleLinkType = ShuttleLink;
12799  if(!CheckHeadCodeValidity(4, GiveMessages, Third))
12800  {
12801  Utilities->CallLogPop(1098);
12802  return(false);
12803  }
12804  Utilities->CallLogPop(1099);
12805  return(true);
12806  }
12807  if(Second == "Fns-sh") // third & fourth have to be headcodes
12808  {
12809  FormatType = FSHNewService;
12810  LocationType = AtLocation;
12811  SequenceType = FinishSequence;
12812  ShuttleLinkType = ShuttleLink;
12813  if(!CheckHeadCodeValidity(5, GiveMessages, Third))
12814  {
12815  Utilities->CallLogPop(1050);
12816  return(false);
12817  }
12818  if(!CheckHeadCodeValidity(6, GiveMessages, Fourth))
12819  {
12820  Utilities->CallLogPop(1051);
12821  return(false);
12822  }
12823  Utilities->CallLogPop(1052);
12824  return(true);
12825  }
12826  // new segment for 'pas'
12827  if(Second == "pas") // third has to be a location
12828  {
12829  FormatType = PassTime;
12830  LocationType = EnRoute;
12831  SequenceType = IntermediateSequence;
12832  ShuttleLinkType = NotAShuttleLink;
12833  if(!CheckLocationValidity(2, Third, GiveMessages, CheckLocationsExistInRailway))
12834  {
12835  Utilities->CallLogPop(1515);
12836  return(false);
12837  }
12838  Utilities->CallLogPop(1516);
12839  return(true);
12840  }
12841  // new segment for revised 'Fer'
12842  if(Second == "Fer")
12843  // third has to be a set of IDs separated by spaces, and at least 1
12844  {
12845  FormatType = ExitRailway;
12846  LocationType = EnRoute;
12847  SequenceType = FinishSequence;
12848  ShuttleLinkType = NotAShuttleLink;
12849  if(CheckLocationsExistInRailway)
12850  {
12851  if(!CheckAndPopulateListOfIDs(0, Third, ExitList, GiveMessages))
12852  {
12853  Utilities->CallLogPop(1519);
12854  return(false);
12855  }
12856  }
12857  Utilities->CallLogPop(1520);
12858  return(true);
12859  }
12860  if(Second == "dsc") //new at v2.15.0 - change description
12861  {
12862  if(Third.Length() > 60)
12863  {
12864  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters in '" + Third + "'");
12865  Utilities->CallLogPop(2582);
12866  return(false);
12867  }
12868  for(int x = 1; x < Third.Length() + 1; x++)
12869  {
12870 // if((Third[x] < ' ') || (Third[x] > '~')) changed at v2.16.0 to allow extended characters in location names
12871  if((Third[x] < ' ') && (Third[x] >= 0))
12872  {
12873  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + Third + "'");
12874  Utilities->CallLogPop(2583);
12875  return(false);
12876  }
12877  }
12878  FormatType = TimeCmdDescription;
12879  LocationType = AtLocation;
12880  SequenceType = IntermediateSequence;
12881  ShuttleLinkType = NotAShuttleLink;
12882  Utilities->CallLogPop(2604);
12883  return(true);
12884  }
12885  if(Second == "cms") //new at v2.21.0 - change max speed
12886  {
12887  if(Third == "")
12888  {
12889  TimetableMessage(GiveMessages, "New maximum speed value missing");
12890  Utilities->CallLogPop(2704);
12891  return(false);
12892  }
12893  for(int x = 1; x < Third.Length() + 1; x++)
12894  {
12895  if((Third[x] < '0') || (Third[x] > '9'))
12896  {
12897  TimetableMessage(GiveMessages, "Train maximum speed must be a number in '" + Third + "'");
12898  Utilities->CallLogPop(2705);
12899  return(false);
12900  }
12901  }
12902  int MaxRunningSpeed = Third.ToInt(); //must be a whole positive number here
12903  if(MaxRunningSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
12904  {
12905  TimetableMessage(GiveMessages, "Train maximum running speed [" + Third + "km/h] can't be greater than 400km/h");
12906  Utilities->CallLogPop(2706);
12907  return(false);
12908  }
12909  if(MaxRunningSpeed < 10)
12910  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
12911  {
12912  TimetableMessage(GiveMessages, "Train maximum running speed [" + Third + "km/h] can't be less than 10km/h.");
12913  Utilities->CallLogPop(2707);
12914  return(false);
12915  }
12916  FormatType = TimeCmdMaxSpeed;
12917  LocationType = AtLocation;
12918  SequenceType = IntermediateSequence;
12919  ShuttleLinkType = NotAShuttleLink;
12920  Utilities->CallLogPop(2708);
12921  return(true);
12922  }
12923 
12924 // if((Second == "fsp") || (Second == "fsp")) then there can optionlly be a fourth: xx-yy where xx = percentage mass & yy = percentage power in the split train
12925 
12926  // all remainder must be TimeCmdHeadCode types to be valid
12927  if((Second != "Fns") && (Second != "Fjo") && (Second != "jbo") && (Second != "fsp") && (Second != "rsp") && (Second != "Sfs") && (Second != "Sns") &&
12928  (Second != "Frh-sh"))
12929  {
12930  Utilities->CallLogPop(922);
12931  return(false); // all TimeCmdHeadCode types
12932  }
12933  if(!CheckHeadCodeValidity(7, GiveMessages, Third))
12934  {
12935  Utilities->CallLogPop(923);
12936  return(false);
12937  }
12938  FormatType = TimeCmdHeadCode;
12939  LocationType = AtLocation;
12940  if(Second == "Frh-sh")
12941  {
12942  ShuttleLinkType = ShuttleLink;
12943  }
12944  else
12945  {
12946  ShuttleLinkType = NotAShuttleLink;
12947  }
12948  if((Second == "Fns") || (Second == "Fjo") || (Second == "Frh-sh"))
12949  {
12950  SequenceType = FinishSequence;
12951  }
12952  if((Second == "jbo") || (Second == "fsp") || (Second == "rsp"))
12953  {
12954  SequenceType = IntermediateSequence;
12955  }
12956  if((Second == "Sfs") || (Second == "Sns"))
12957  {
12958  SequenceType = StartSequence;
12959  }
12960  //new at v2.15.0 to allow splits to have different characteristics - Fourth specifies: AA-BB where AA & BB can be 1 or 2 digits, AA is percentage mass
12961  if((Fourth != "") && ((Second == "fsp") || (Second == "rsp"))) //& BB is percentage power allocated to the split off train
12962  {
12963  if(!CheckFourthValidityForSplit(Fourth, GiveMessages))
12964  {
12965  Utilities->CallLogPop(2584);
12966  return(false);
12967  }
12968  }
12969  Utilities->CallLogPop(924);
12970  return(true);
12971 }
12972 
12973 // ---------------------------------------------------------------------------
12974 
12975 bool TTrainController::NotACommand(int Caller, AnsiString Text)
12976 {
12977  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",NotACommand");
12978  if((Text == "Snt") || (Text == "Sfs") || (Text == "Sns") || (Text == "Sns-fsh") || (Text == "Snt-sh") || (Text == "Sns-sh") ||
12979  (Text == "pas") || (Text == "jbo") || (Text == "fsp") || (Text == "rsp") || (Text == "cdt") || (Text == "dsc") ||
12980  (Text == "cms") || (Text == "Fns") || (Text == "Fjo") || (Text == "Fer") || (Text == "Frh-sh") || (Text == "Fns-sh") ||
12981  (Text == "F-nshs") || (Text == "Frh"))
12982  {
12983  Utilities->CallLogPop(2738);
12984  return(false);
12985  }
12986  Utilities->CallLogPop(2739);
12987  return(true);
12988 }
12989 
12990 // ---------------------------------------------------------------------------
12991 
12992 bool TTrainController::CheckFourthValidityForSplit(AnsiString SplitDistributionString, bool GiveMessages) //new at v2.15.0
12993 {
12994  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",CheckFourthValidityForSplit," + SplitDistributionString);
12995  bool ErrorFlag = false;
12996  int x, y;
12997  if((SplitDistributionString.Length() > 6 ) || (SplitDistributionString.Length() < 3))
12998  {
12999  ErrorFlag = true;
13000  }
13001  int pos = SplitDistributionString.Pos('-');
13002  if(pos == 0)
13003  {
13004  ErrorFlag = true;
13005  }
13006  else
13007  {
13008  AnsiString MassStr = SplitDistributionString.SubString(1, pos - 1);
13009  AnsiString PowerStr = SplitDistributionString.SubString(pos + 1, SplitDistributionString.Length() - pos);
13010  try //allows for one or two digit percentages
13011  {
13012  int x = MassStr.ToInt();
13013  int y = PowerStr.ToInt();
13014  if((x > 99) || (x < 1) || (y > 100) || (y < 0))
13015  {
13016  ErrorFlag = true;
13017  }
13018  }
13019  catch(const Exception &e) //non-error catch
13020  {
13021  ErrorFlag = true;
13022  }
13023  }
13024  if(ErrorFlag)
13025  {
13026  TimetableMessage(GiveMessages, "Error in split distribution " + SplitDistributionString + ", should be 'AA-BB' where AA is the percentage mass (min 1, max 99) and BB the percentage " +
13027  "power for the new split-off train");
13028  Utilities->CallLogPop(2585);
13029  return(false);
13030  }
13031  Utilities->CallLogPop(2601);
13032  return(true);
13033 }
13034 
13035 // ---------------------------------------------------------------------------
13036 
13037 bool TTrainController::CheckLocationValidity(int Caller, AnsiString LocStr, bool GiveMessages, bool CheckLocationsExistInRailway)
13038 {
13039  // check that the location name exists in the railway (only if CheckLocationsExistInRailway is true), doesn't begin with 'digit-digit-colon'
13040  // and contains no control characters changed at v2.16.0 to allow extended characters in location names
13041  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckLocationValidity," + LocStr);
13042  if(LocStr == "")
13043  {
13044  Utilities->CallLogPop(1353);
13045  return(false); // has to have at least one character
13046  }
13047 // if((LocStr[1] >= '0') && (LocStr[1] <= '9')) //changed at v2.16.0 to allow locations to begine with digits, if 'digit-digit-colon' then must be a time
13048  if((LocStr.Length() >= 3) && (LocStr[1] >= '0') && (LocStr[1] <= '9') && (LocStr[2] >= '0') && (LocStr[2] <= '9') && (LocStr[3] == ':'))
13049  {
13050  Utilities->CallLogPop(1354);
13051  return(false); // can't begin with 'digit-digit-colon' as this regarded as a time
13052  }
13053  for(int x = 1; x < LocStr.Length() + 1; x++)
13054  {
13055  if(((LocStr[x] < ' ') && (LocStr[x] >= 0)) || (LocStr[x] == ',') || (LocStr[x] == ';')) //changed at v2.16.0 to allow extended characters in location names
13056  {
13057  Utilities->CallLogPop(1355);
13058  return(false); // contains a special character or ',' or ';'
13059  }
13060 /*
13061  if(LocStr[x] > 'z') //dropped at v2.16.0 to allow extended characters in location names
13062  {
13063  Utilities->CallLogPop(1356);
13064  return(false); // contains a character outside the standard ASCII set
13065  }
13066 */
13067  }
13068  // check exists in railway location list if CheckLocationsExistInRailway is true
13069  if(CheckLocationsExistInRailway)
13070  {
13071  if(!Track->TimetabledLocationNameAllocated(3, LocStr))
13072  {
13073  TimetableMessage(GiveMessages, "Location name '" + LocStr +
13074  "' appears in the timetable but is not a valid name. To be valid the name must be a stopping location and apply to one or more platforms " +
13075  "(not concourses on their own), or to track at a blue non-station named location. BUT NOTE THAT trains can't stop at continuations so a name " +
13076  "that includes a continuation will not be valid.");
13077  Utilities->CallLogPop(1357);
13078  return(false);
13079  }
13080  }
13081  Utilities->CallLogPop(1358);
13082  return(true);
13083 }
13084 
13085 // ---------------------------------------------------------------------------
13086 
13087 bool TTrainController::CheckHeadCodeValidity(int Caller, bool GiveMessages, AnsiString HeadCode)
13088 {
13089  // if(!AnyHeadCodeValid) up to 8 characters total & last 4 characters must be NLNN where N = number and L = capital or small letter
13090  // if(AnyHeadCodeValid) up to 8 characters total, last 2 chars must be digits & last but 2 can be any alphanumeric, upper or lower case
13091  // NOTE: As of v0.6b AnyHeadCodeValid dropped, all headcodes are unrestricted
13092  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString((short)GiveMessages) + ",CheckHeadCodeValidity," +
13093  HeadCode);
13094  if((HeadCode.Length() < 4) || (HeadCode.Length() > 8))
13095  {
13096  TimetableMessage(GiveMessages, "Headcode error in '" + HeadCode +
13097  "', length must be between 4 and 8 characters, and last 4 must be a legitimate headcode. This error can also be caused by omitting a service reference after Sns, Snt-sh, Sns-sh, Fns, Fns-sh or Frh-sh");
13098  Utilities->CallLogPop(1359);
13099  return(false);
13100  }
13101  // firstly allow any printable character (ASCII >= CHAR(32) & <= CHAR(126)), as these allowed in 1st 4 characters
13102  for(int x = 1; x < (HeadCode.Length() + 1); x++)
13103  {
13104  if((HeadCode[x] < ' ') || (HeadCode[x] > '~'))
13105  {
13106  TimetableMessage(GiveMessages, "Non-printable character in headcode '" + HeadCode + "'");
13107  Utilities->CallLogPop(1895);
13108  return(false);
13109  }
13110  }
13111  // secondly ensure the true Headcode only has letters or digits
13112  for(int x = 3; x >= 0; x--)
13113  {
13114  if(((HeadCode[HeadCode.Length() - x] < 'A') || (HeadCode[HeadCode.Length() - x] > 'Z')) && ((HeadCode[HeadCode.Length() - x] < 'a') ||
13115  (HeadCode[HeadCode.Length() - x] > 'z')) && ((HeadCode[HeadCode.Length() - x] < '0') || (HeadCode[HeadCode.Length() - x] > '9')))
13116  {
13117  TimetableMessage(GiveMessages, "Headcode error in '" + HeadCode + "', headcode must consist of letters and digits only");
13118  Utilities->CallLogPop(1790);
13119  return(false);
13120  }
13121  }
13122  Utilities->CallLogPop(1364);
13123  return(true);
13124 }
13125 
13126 // ---------------------------------------------------------------------------
13127 
13128 bool TTrainController::CheckAndPopulateListOfIDs(int Caller, AnsiString IDSet, TNumList &ExitList, bool GiveMessages)
13129 // set of track element IDs, separated by spaces, and at least 1 present
13130 {
13131  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckAndPopulateListOfIDs," + IDSet); //had wrong title, changed at v2.13.0
13132  ExitList.clear();
13133  AnsiString CurrentID = "";
13134 
13135  if(IDSet.Length() == 0)
13136  {
13137  TimetableMessage(GiveMessages, "Must have at least one exit element ID following 'Fer'");
13138  Utilities->CallLogPop(1521);
13139  return(false);
13140  }
13141  for(int x = 1; x <= IDSet.Length(); x++)
13142  {
13143  char C = IDSet[x];
13144  if(((C < '0') || (C > '9')) && (C != ' ') && (C != 'N') && (C != '-'))
13145  {
13146  TimetableMessage(GiveMessages, "Illegal character in the set of element IDs following 'Fer' in '" + IDSet + "'");
13147  Utilities->CallLogPop(1522);
13148  return(false);
13149  }
13150 /* don't use, error checks in GetTrackVectorPositionFromString instead
13151  if(C == '-') //this section added at v2.13.0 because of Amon Sadler's error file submitted 24/03/22
13152  {
13153  if((x==1) || (x == IDSet.Length()))
13154  {
13155  TimetableMessage(GiveMessages, "Illegal minus character ('-') in the set of element IDs following 'Fer' in '" + IDSet + "'");
13156  Utilities->CallLogPop(2479);
13157  return(false);
13158  }
13159  if((IDSet[x-1] < '0') || (IDSet[x-1] > '9') || (IDSet[x+1] < '0') || (IDSet[x+1] > '9'))
13160  {
13161  TimetableMessage(GiveMessages, "Illegal minus character ('-') in the set of element IDs following 'Fer' in '" + IDSet + "'");
13162  Utilities->CallLogPop(2480);
13163  return(false);
13164  }
13165  }
13166 */
13167  }
13168  int Pos = IDSet.Pos(' '); // look for the first space
13169 
13170  while(true)
13171  {
13172  if(Pos == 0)
13173  {
13174  CurrentID = IDSet;
13175  IDSet = "";
13176  }
13177  else
13178  {
13179  CurrentID = IDSet.SubString(1, Pos - 1);
13180  IDSet = IDSet.SubString(Pos + 1, IDSet.Length() - Pos);
13181  }
13182  int VecPos = Track->GetTrackVectorPositionFromString(7, CurrentID, GiveMessages);
13183  if(VecPos == -1)
13184  {
13185  Utilities->CallLogPop(1523);
13186  return(false); // messages given in GetTrackVectorPositionFromString
13187  }
13188  else
13189  {
13190  if(Track->TrackElementAt(722, VecPos).TrackType != Continuation)
13191  {
13192  TimetableMessage(GiveMessages, "The element ID '" + CurrentID + "' following 'Fer' is not an exit");
13193  Utilities->CallLogPop(1524);
13194  return(false);
13195  }
13196  else
13197  {
13198  // first check for duplicates
13199  if(!ExitList.empty())
13200  {
13201  for(TNumListIterator ELIT = ExitList.begin(); ELIT != ExitList.end(); ELIT++)
13202  {
13203  if(*ELIT == VecPos)
13204  {
13205  TimetableMessage(GiveMessages, "The element ID '" + CurrentID + "' following 'Fer' duplicates an earlier element");
13206  Utilities->CallLogPop(1532);
13207  return(false);
13208  }
13209  }
13210  }
13211  // of OK add it to the list
13212  ExitList.push_back(VecPos);
13213  }
13214  }
13215  if(IDSet == "")
13216  {
13217  Utilities->CallLogPop(1525);
13218  return(true);
13219  }
13220  else
13221  {
13222  Pos = IDSet.Pos(' '); // look for the next space
13223  }
13224  } // while(true)
13225 }
13226 
13227 // ---------------------------------------------------------------------------
13228 bool TTrainController::SplitTrainInfo(int Caller, AnsiString TrainInfoStr, AnsiString &HeadCode, AnsiString &Description, int &StartSpeed, int &MaxRunningSpeed,
13229  int &Mass, double &MaxBrakeRate, double &PowerAtRail, int &SignallerSpeed, bool GiveMessages)
13230 // 7 or 8 items for a new train (6 or 7 semicolons), for a continuing service only need headcode, though can have a description, if other
13231 // data entered for continuing service then will be ignored - message given to warn user, checks appropriate number of items and validity
13232 // of each item
13233 {
13234  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitTrainInfo," + TrainInfoStr);
13235  int Pos = 0;
13236  AnsiString Remainder = "";
13237  int SemiColonCount = 0;
13238 
13239  for(int x = 1; x < TrainInfoStr.Length() + 1; x++)
13240  {
13241  if(TrainInfoStr[x] == ';')
13242  {
13243  SemiColonCount++;
13244  }
13245  }
13246  if((SemiColonCount != 6) && (SemiColonCount != 7) && (SemiColonCount != 1) && (SemiColonCount != 0))
13247  {
13248  TimetableMessage(GiveMessages, "Error in train information in '" + TrainInfoStr +
13249  "'. Should be headcode + optional description for a continuing service;" +
13250  " or headcode, description, start speed, max running speed, mass, brake force, power (and optional signaller max. speed) for a new service");
13251  Utilities->CallLogPop(880);
13252  return(false);
13253  }
13254  if(SemiColonCount == 0)
13255  {
13256  HeadCode = TrainInfoStr;
13257  if(!CheckHeadCodeValidity(8, GiveMessages, HeadCode))
13258  {
13259  Utilities->CallLogPop(881);
13260  return(false);
13261  }
13262  Utilities->CallLogPop(882);
13263  return(true);
13264  }
13265  if(SemiColonCount == 1) // headcode & description only
13266  {
13267  Pos = TrainInfoStr.Pos(';'); // 1st delimiter
13268  HeadCode = TrainInfoStr.SubString(1, Pos - 1);
13269  Description = TrainInfoStr.SubString(Pos + 1, TrainInfoStr.Length() - Pos);
13270  if(!CheckHeadCodeValidity(9, GiveMessages, HeadCode))
13271  {
13272  Utilities->CallLogPop(883);
13273  return(false);
13274  }
13275  if(Description == "")
13276  {
13277  TimetableMessage(GiveMessages, "Train description missing in '" + TrainInfoStr + "'");
13278  Utilities->CallLogPop(884);
13279  return(false);
13280  }
13281  if(Description.Length() > 60)
13282  {
13283  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters '" + TrainInfoStr + "'");
13284  Utilities->CallLogPop(1157);
13285  return(false);
13286  }
13287  for(int x = 1; x < Description.Length() + 1; x++)
13288  {
13289 // if((Description[x] < ' ') || (Description[x] > '~')) changed at v2.16.0 to allow extended characters in location names
13290  if((Description[x] < ' ') && (Description[x] >= 0))
13291  {
13292  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + TrainInfoStr + "'");
13293  Utilities->CallLogPop(885);
13294  return(false);
13295  }
13296  }
13297  Utilities->CallLogPop(886);
13298  return(true);
13299  }
13300  // if here must have 6 or 7 semicolons
13301  Pos = TrainInfoStr.Pos(';'); // 1st delimiter
13302  HeadCode = TrainInfoStr.SubString(1, Pos - 1);
13303  Remainder = TrainInfoStr.SubString(Pos + 1, TrainInfoStr.Length() - Pos);
13304  if(!CheckHeadCodeValidity(10, GiveMessages, HeadCode))
13305  {
13306  Utilities->CallLogPop(887);
13307  return(false);
13308  }
13309  Pos = Remainder.Pos(';'); // 2nd delimiter
13310  Description = Remainder.SubString(1, Pos - 1);
13311  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13312  if(Description == "")
13313  {
13314  TimetableMessage(GiveMessages, "Train description missing in '" + TrainInfoStr + "'");
13315  Utilities->CallLogPop(888);
13316  return(false);
13317  }
13318  if(Description.Length() > 60)
13319  {
13320  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters '" + TrainInfoStr + "'");
13321  Utilities->CallLogPop(1158);
13322  return(false);
13323  }
13324  for(int x = 1; x < Description.Length() + 1; x++)
13325  {
13326 // if((Description[x] < ' ') || (Description[x] > 126)) changed at v2.16.0 to allow extended characters in location names
13327  if((Description[x] < ' ') && (Description[x] >= 0))
13328  {
13329  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + TrainInfoStr + "'");
13330  Utilities->CallLogPop(889);
13331  return(false);
13332  }
13333  }
13334  Pos = Remainder.Pos(';'); // 3rd delimiter
13335  AnsiString StartSpeedStr = Remainder.SubString(1, Pos - 1);
13336 
13337  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13338  if(StartSpeedStr == "")
13339  {
13340  TimetableMessage(GiveMessages, "Train starting speed missing in '" + TrainInfoStr + "'");
13341  Utilities->CallLogPop(890);
13342  return(false);
13343  }
13344  for(int x = 1; x < StartSpeedStr.Length() + 1; x++)
13345  {
13346  if((StartSpeedStr[x] < '0') || (StartSpeedStr[x] > '9'))
13347  {
13348  TimetableMessage(GiveMessages, "Train start speed contains invalid characters in '" + TrainInfoStr + "'");
13349  Utilities->CallLogPop(891);
13350  return(false);
13351  }
13352  }
13353  StartSpeed = StartSpeedStr.ToInt();
13354  if(StartSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
13355  {
13356  StartSpeed = TTrain::MaximumSpeedLimit;
13357  if(!SSHigh) // added at v2.4.0
13358  {
13359  TimetableMessage(GiveMessages, "Train starting speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
13360  SSHigh = true;
13361  }
13362  }
13363  Pos = Remainder.Pos(';'); // 4th delimiter
13364  AnsiString MaxRunningSpeedStr = Remainder.SubString(1, Pos - 1);
13365 
13366  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13367  if(MaxRunningSpeedStr == "")
13368  {
13369  TimetableMessage(GiveMessages, "Train maximum running speed missing in '" + TrainInfoStr + "'");
13370  Utilities->CallLogPop(892);
13371  return(false);
13372  }
13373  for(int x = 1; x < MaxRunningSpeedStr.Length() + 1; x++)
13374  {
13375  if((MaxRunningSpeedStr[x] < '0') || (MaxRunningSpeedStr[x] > '9'))
13376  {
13377  TimetableMessage(GiveMessages, "Train maximum running speed contains invalid characters in '" + TrainInfoStr + "'");
13378  Utilities->CallLogPop(893);
13379  return(false);
13380  }
13381  }
13382  MaxRunningSpeed = MaxRunningSpeedStr.ToInt();
13383  if(MaxRunningSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
13384  {
13385  TimetableMessage(GiveMessages, "Train maximum running speed [" + MaxRunningSpeedStr + "km/h] can't be greater than 400km/h");
13386  Utilities->CallLogPop(2709);
13387  return(false);
13388  }
13389  if(MaxRunningSpeed < 10)
13390  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
13391  {
13392  TimetableMessage(GiveMessages, "Train maximum running speed [" + MaxRunningSpeedStr + "km/h] can't be less than 10km/h.");
13393  Utilities->CallLogPop(2710);
13394  return(false);
13395  }
13396  Pos = Remainder.Pos(';'); // 5th delimiter
13397  AnsiString MassStr = Remainder.SubString(1, Pos - 1);
13398 
13399  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13400  if(MassStr == "")
13401  {
13402  TimetableMessage(GiveMessages, "Train mass missing in '" + TrainInfoStr + "'");
13403  Utilities->CallLogPop(895);
13404  return(false);
13405  }
13406  for(int x = 1; x < MassStr.Length() + 1; x++)
13407  {
13408  if((MassStr[x] < '0') || (MassStr[x] > '9'))
13409  {
13410  TimetableMessage(GiveMessages, "Train mass contains invalid characters in '" + TrainInfoStr + "'");
13411  Utilities->CallLogPop(896);
13412  return(false);
13413  }
13414  }
13415  Mass = MassStr.ToInt() * 1000; // convert tonnes to kg
13416  if(Mass > TTrain::MaximumMassLimit) // 10,000tonnes
13417  {
13418  Mass = TTrain::MaximumMassLimit;
13419  if(!MassHigh) // added at v2.4.0
13420  {
13421  TimetableMessage(GiveMessages, "Train mass > 10,000 tonnes in '" + TrainInfoStr + "'. Setting it to 10,000 tonnes");
13422  MassHigh = true;
13423  }
13424  }
13425  if(Mass == 0)
13426  {
13427  TimetableMessage(GiveMessages, "Train mass zero in '" + TrainInfoStr + "'");
13428  Utilities->CallLogPop(897);
13429  return(false);
13430  }
13431  Pos = Remainder.Pos(';'); // 6th delimiter
13432  AnsiString MaxBrakeForceStr = Remainder.SubString(1, Pos - 1);
13433 
13434  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13435  if(MaxBrakeForceStr == "")
13436  {
13437  TimetableMessage(GiveMessages, "Train braking force missing in '" + TrainInfoStr + "'");
13438  Utilities->CallLogPop(898);
13439  return(false);
13440  }
13441  for(int x = 1; x < (MaxBrakeForceStr.Length() + 1); x++)
13442  {
13443  if((MaxBrakeForceStr[x] != '.') && ((MaxBrakeForceStr[x] < '0') || (MaxBrakeForceStr[x] > '9')))
13444  {
13445  TimetableMessage(GiveMessages, "Train braking force contains invalid characters in '" + TrainInfoStr + "'");
13446  Utilities->CallLogPop(899);
13447  return(false);
13448  }
13449  }
13450  double MaxBrakeForce = MaxBrakeForceStr.ToDouble() * 1000;
13451 
13452  // convert to kg force
13453  if((MaxBrakeForce / Mass) > 1) // gives 'g' braking - 9.81m/s/s
13454  {
13455  MaxBrakeForce = Mass;
13456  if(!BFHigh) // added at v2.4.0
13457  {
13458  TimetableMessage(GiveMessages, "Train braking force too high in '" + TrainInfoStr + "'. Setting it to the same as the train mass");
13459  BFHigh = true;
13460  }
13461  }
13462  if((MaxBrakeForce / Mass) < 0.01)
13463  {
13464  MaxBrakeForce = Mass * 0.01;
13465  if(!BFLow) // added at v2.4.0
13466  {
13467  TimetableMessage(GiveMessages, "Train braking force too low in '" + TrainInfoStr + "'. Setting it to 1% of the train mass");
13468  BFLow = true;
13469  }
13470  }
13471  // convert to m/s/s
13472  MaxBrakeRate = MaxBrakeForce / Mass * 9.81;
13473  // now may have just a power entry or power and signaller max. speed
13474  AnsiString GrossPowerStr = "", SignallerSpeedStr = "";
13475 
13476  if(SemiColonCount == 6)
13477  {
13478  GrossPowerStr = Remainder;
13479  SignallerSpeedStr = "30"; // default value
13480  }
13481  else // must be 7
13482  {
13483  Pos = Remainder.Pos(';'); // 7th delimiter
13484  GrossPowerStr = Remainder.SubString(1, Pos - 1);
13485  SignallerSpeedStr = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13486  }
13487  // deal with GrossPower
13488  if(GrossPowerStr == "")
13489  {
13490  TimetableMessage(GiveMessages, "Train power missing in '" + TrainInfoStr + "'");
13491  Utilities->CallLogPop(901);
13492  return(false);
13493  }
13494  for(int x = 1; x < GrossPowerStr.Length() + 1; x++)
13495  {
13496  if((GrossPowerStr[x] < '0') || (GrossPowerStr[x] > '9'))
13497  {
13498  TimetableMessage(GiveMessages, "Train power contains invalid characters in '" + TrainInfoStr + "'");
13499  Utilities->CallLogPop(902);
13500  return(false);
13501  }
13502  }
13503 
13504  double GrossPower = GrossPowerStr.ToInt() * 1000; // convert to W
13505 
13506  if(GrossPower > TTrain::MaximumPowerLimit) // 100MW
13507  {
13508  GrossPower = TTrain::MaximumPowerLimit;
13509  if(!PwrHigh)
13510  {
13511  TimetableMessage(GiveMessages, "Train power > 100,000kW in '" + TrainInfoStr + "'. Setting it to 100,000kW");
13512  PwrHigh = true;
13513  }
13514  }
13515  else if(GrossPower == 0) // changed at v2.4.0
13516  {
13517  GrossPower = 0.1;
13518  // can't be zero or AValue is zero and then have divide by zero error, so set to 0.1W so acceleration tiny (though should be intercepted before accel calculated)
13519  }
13520  else if((GrossPower > 0) && (GrossPower < 10000))
13521  // added at v2.4.0 to ensure min power of 8kW at rail unless zero (otherwise could have too low AValues
13522  {
13523  GrossPower = 10000;
13524  }
13525  PowerAtRail = GrossPower * 0.8;
13526  // apply ratio of 80% for rail to gross power (seems about average from an internet search)
13527 
13528  // deal with SignallerSpeed
13529  if(SignallerSpeedStr == "")
13530  {
13531  TimetableMessage(GiveMessages, "Signaller speed not set in '" + TrainInfoStr + "', either set a value or remove the extra semicolon");
13532  Utilities->CallLogPop(1771);
13533  return(false);
13534  }
13535  for(int x = 1; x < SignallerSpeedStr.Length() + 1; x++)
13536  {
13537  if((SignallerSpeedStr[x] < '0') || (SignallerSpeedStr[x] > '9'))
13538  {
13539  TimetableMessage(GiveMessages, "Signaller speed contains invalid characters in '" + TrainInfoStr + "'");
13540  Utilities->CallLogPop(1769);
13541  return(false);
13542  }
13543  }
13544  SignallerSpeed = SignallerSpeedStr.ToInt();
13545  if(SignallerSpeed > TTrain::MaximumSpeedLimit)
13546  {
13547  SignallerSpeed = TTrain::MaximumSpeedLimit;
13548  if(!SigSHigh)
13549  {
13550  TimetableMessage(GiveMessages, "Signaller speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
13551  SigSHigh = true;
13552  }
13553  }
13554  if(SignallerSpeed < 10)
13555  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
13556  {
13557  SignallerSpeed = 10;
13558  if(!SigSLow)
13559  {
13560  TimetableMessage(GiveMessages, "Signaller speed can't be less than 10km/h in '" + TrainInfoStr + "', it will be set to 10km/h");
13561  SigSLow = true;
13562  }
13563  // Utilities->CallLogPop(1770);
13564  // return false;
13565  }
13566  Utilities->CallLogPop(904);
13567  return(true);
13568 }
13569 
13570 // ---------------------------------------------------------------------------
13571 
13572 bool TTrainController::SplitRepeat(int Caller, AnsiString OneEntry, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, int &NumberOfRepeats,
13573  bool GiveMessages)
13574 {
13575  // Format must be: R;mm;dd;nn mm may be 1, 2 or more digits, dd may be 1 or 2 digits, nn may be 1, 2 or more digits
13576  // function checks validity of each item and returns false for error
13577  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitRepeat," + OneEntry);
13578  if(OneEntry.Length() < 7)
13579  {
13580  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
13581  Utilities->CallLogPop(865);
13582  return(false);
13583  }
13584  int SemiColonCount = 0;
13585 
13586  for(int x = 1; x < OneEntry.Length() + 1; x++)
13587  {
13588  if(OneEntry[x] == ';')
13589  {
13590  SemiColonCount++;
13591  }
13592  }
13593  if(SemiColonCount != 3)
13594  {
13595  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
13596  Utilities->CallLogPop(866);
13597  return(false);
13598  }
13599  if((OneEntry[1] != 'R') || (OneEntry[2] != ';'))
13600  {
13601  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
13602  Utilities->CallLogPop(867);
13603  return(false);
13604  }
13605  AnsiString Remainder = OneEntry.SubString(3, OneEntry.Length() - 2);
13606  // strip off R;
13607 
13608  int Pos = 0;
13609 
13610  Pos = Remainder.Pos(';');
13611  AnsiString MinutesStr = Remainder.SubString(1, Pos - 1);
13612 
13613  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13614  if(MinutesStr == "")
13615  {
13616  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - minute increment segment missing");
13617  Utilities->CallLogPop(868);
13618  return(false);
13619  }
13620  if(MinutesStr.Length() > 3)
13621  // added for v2.3.1 following Albie Vowles' reported error in repeat value 03/02/20
13622  {
13623  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - minute value too high, maximum value is 999");
13624  Utilities->CallLogPop(2119);
13625  return(false);
13626  }
13627  for(int x = 1; x < MinutesStr.Length() + 1; x++)
13628  {
13629  if((MinutesStr[x] < '0') || (MinutesStr[x] > '9'))
13630  {
13631  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in minute increment segment");
13632  Utilities->CallLogPop(869);
13633  return(false);
13634  }
13635  }
13636  RearStartOrRepeatMins = MinutesStr.ToInt();
13637  if(RearStartOrRepeatMins == 0)
13638  {
13639  TimetableMessage(GiveMessages, "Repeat minute increment is zero in: '" + OneEntry + "' - can't have a zero value");
13640  Utilities->CallLogPop(870);
13641  return(false);
13642  }
13643  Pos = Remainder.Pos(';');
13644  AnsiString DigitsStr = Remainder.SubString(1, Pos - 1);
13645 
13646  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13647  if(DigitsStr == "")
13648  {
13649  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - headcode increment segment missing");
13650  Utilities->CallLogPop(871);
13651  return(false);
13652  }
13653  for(int x = 1; x < DigitsStr.Length() + 1; x++)
13654  {
13655  if((DigitsStr[x] < '0') || (DigitsStr[x] > '9'))
13656  {
13657  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in headcode increment segment");
13658  Utilities->CallLogPop(872);
13659  return(false);
13660  }
13661  }
13662  if(DigitsStr.Length() > 2)
13663  {
13664  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - maximum number of digits for headcode increment is 2");
13665  Utilities->CallLogPop(873);
13666  return(false);
13667  }
13668  FrontStartOrRepeatDigits = DigitsStr.ToInt();
13669 /* allow zero digit increments so HC can stay same for repeated services - for many suburban services the headcode digits relate to the
13670  route rather than the service
13671  if(FrontStartOrRepeatDigits == 0)
13672  {
13673  TimetableMessage(GiveMessages, "Repeat headcode increment is zero in: '" + OneEntry + "' - can't have a zero value");
13674  Utilities->CallLogPop(874);
13675  return false;
13676  }
13677 */
13678  if(!Last2CharactersBothDigits(0, ServiceReference) && (FrontStartOrRepeatDigits > 0))
13679  // new for v0.6b for unrestricted headcodes
13680  {
13681  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry +
13682  "' - a repeating service with incrementing digits must have digits as its last two headcode characters");
13683  Utilities->CallLogPop(1889);
13684  return(false);
13685  }
13686  AnsiString NumberStr = Remainder;
13687 
13688  if(NumberStr == "")
13689  {
13690  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - number of repeats missing");
13691  Utilities->CallLogPop(875);
13692  return(false);
13693  }
13694  if(NumberStr.Length() > 4)
13695  // added for v2.3.1 following Albie Vowles' reported error 03/02/20
13696  {
13697  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - repeat value too high, no timetabled event can exceed 95 hours & 59 minutes");
13698  Utilities->CallLogPop(2118);
13699  return(false);
13700  }
13701  for(int x = 1; x < NumberStr.Length() + 1; x++)
13702  {
13703  if((NumberStr[x] < '0') || (NumberStr[x] > '9'))
13704  // catches negative numbers
13705  {
13706  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in number of repeats");
13707  Utilities->CallLogPop(876);
13708  return(false);
13709  }
13710  }
13711  NumberOfRepeats = NumberStr.ToInt();
13712  if(NumberOfRepeats == 0)
13713  {
13714  TimetableMessage(GiveMessages, "Number of repeats is zero in: '" + OneEntry + "' - if no repeats are needed the repeat should be omitted");
13715  Utilities->CallLogPop(877);
13716  return(false);
13717  }
13718  Utilities->CallLogPop(878);
13719  return(true);
13720 }
13721 
13722 // ---------------------------------------------------------------------------
13723 
13724 bool TTrainController::SecondPassActions(int Caller, bool GiveMessages, bool &TwoLocationFlag) //TwoLocationFlag added at v2.9.1
13725 /* Note that here the TrainDataVector has been compiled with FinalCall true in ProcessOneTimetableLine so work on the
13726  vector rather than the timetable
13727  Note also that for unlocated Snt entries the LocationType hasn't yet been set
13728 
13729  Many of the errors caught here duplicate those in the preliminary checks, but leave in for completeness
13730 
13731 Updated significantly for v2.15.0. Current procedure:-
13732 
13733 Preliminary checks for v0.2b without changing anything, carry each out separately:-
13734  1) must have at least one actionvector entry
13735  2) if first actionvector entry not SignallerControl then must have at least one more actionvector entry
13736  3) if first actionvector entry is SignallerControl then must have no more actionvector entries except a repeat
13737  4) first entry must be a start;
13738  4a) if first entry is Snt and second is a finish then it can't be Fns-sh or Frh-sh
13739  4b) if first entry is Sns or Sfs and second is a finish then it must be either Frh or Fjo
13740  4c) if first entry is Snt-sh, Sns-sh or Sns-fsh second can't be a finish
13741  5) a start must be the first entry;
13742  6) a repeat entry must be the last;
13743  7) for other than SignallerControl the last entry must be repeat or finish; if last entry is a repeat the last but one must be a finish;
13744  8) a finish entry must be the last or last but one, and if last but one the last must be a repeat
13745  Other successor errors will be caught later as all 'throws' changed to messages prior to the bulk of the sucessor checks
13746 
13747 Set location for located Snt or Snt-sh and ensure successor AtLocation
13748 For unlocated Snt-sh give error message
13749 For unlocated Snt & not sig control check successor moving
13750 
13751 Check all other starts (all located) have valid successors
13752 
13753 Set location for Sns-sh and Sns-fsh from following TimeLoc, if not one then give message
13754 
13755 Carry out linkage checks to ensure all links present, no data set yet & locations not checked yet. First check for duplicates, then for cross
13756 references, then for non-repeating shuttle cross refs. This is done because the later location naming functions give error messages if there
13757 are missing links.
13758 
13759 Set names for all Fns finishes from earlier named event or fail if can't find
13760 Set names for linked Sns events with same event times from above, but first carry out immediate successor checks and give error message for:-
13761 no successors, moving successor, another start sequence, a finish that isn't Frh or Fjo or a repeat. No error messages given here for location
13762 not found, that check done later.
13763 
13764 Trap errors where rsp/fsp follows an Sfs without a TimeLoc arrival before (or unlikely to be able to set fsp/rsp/Sfs location because Sfs locs set
13765 from linked fsp/rsp events)
13766 
13767 Name all fsp/rsp events, then check that all named or give error message.
13768 
13769 Set all Sfs names from above fsp/rsp links with same event times, but first carry out immediate successor checks and give error message for:-
13770 no successors, moving successor, another start sequence, a finish that isn't Frh or Fjo or a repeat. No error messages given here for location
13771 not found, that check done later.
13772 
13773 Set remaining AtLoc Command locations from preceding named event
13774 
13775 All location names should now be set
13776 
13777 Final detailed check of names for all AtLoc Commands. If find any without a name give an error message:-
13778 If jbo, fsp, rsp, cdt dsc or cms say must be preceded by a named event at same location, normally an arrival
13779 If Sns or Sfs say to make sure the linked finish event is preceded by a named event at same location, normally an arrival
13780 If Snt-sh say to make sure that the service starts with zero speed and is at a named location
13781 If Sns-fsh or Sns-sh say to make sure that the event is followed (not necessarily immediately) by a departure
13782 If Frh, Fns, Fjo, Frh-sh, Fns-sh or F-nshs say that the event must be preceded by an event at the same location that has an identified location name,
13783 normally an arrival.
13784 Missing: pas & Fer not AtLoc, Snt whether located or not covered in detail earlier.
13785 
13786 Later checks as before 2.15.0 changes:-
13787 
13788 Check remaining successor validity except for TimeLoc arr & dep since those times not set yet
13789 
13790 Set arrival & departure times for TimeLocs & set their EventTimes to -1 (up to now all have times as EventTime)
13791 
13792 Perform remaining successor checks for TimeLocs
13793 
13794 Check all TimeLocs have either Arr or Dep time set and EventTime == -1, all Cmds have EventTime set & Arr & Dep times == -1, & repeats have no times
13795 set
13796 
13797 Check times stay same or increase through a service, note that can have time of 0 if include midnight
13798 
13799 Check locations consistent
13800 
13801 Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs (just a potential error warning given in v2.6.0) i.e.
13802 same location can appear in any number of consecutive entries but once changed couldn't repeat before a direction change prior to v2.6.0. Message
13803 given in InterfaceUnit
13804 
13805 Check all locations except unlocated 'Snt' & 'Fer' have LocationName set and throw error if not.
13806 
13807 Carry out full cross reference and duplicate link checks for all services inc shuttles, and set data and check location consistency
13808 
13809 Check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
13810 when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles, don't ever need to and as
13811 designed would skip repeats
13812 
13813 Check all entries have all types set to something and throw error if not
13814 
13815 All OK if reach here, so set up the TrainOperatingDataVector (already has one entry) & NumberOfTrains
13816 
13817 Check that don't include any Continuation names
13818 
13819 Check that all repeat times below 96h
13820 
13821 Now that all set up change any extended headcodes back to ordinary headcodes (had been service references until now.
13822 
13823 Finally call BuildContinuationTrainExpectationMultiMap
13824 
13825 ***********************************
13826 
13827 For info:-
13828 class TActionVectorEntry //contains a single train action - repeat entry is also of this class though no train action is taken for it
13829 {
13830 public:
13831 AnsiString LocationName, Command, OtherHeadCode, NonRepeatingShuttleLinkHeadCode; ///< string values for timetabled event entries, null
13833 bool SignallerControl; ///< indicates a train that is defined by the timetable as under signaller control
13834 bool Warning; ///< if set triggers an alert in the warning panel when the action is reached
13835 int NumberOfRepeats; ///< the number of repeating services
13836 int RearStartOrRepeatMins, FrontStartOrRepeatDigits; ///< dual-purpose variables used for the TrackVectorPositions of the rear and front
13838 TDateTime EventTime, ArrivalTime, DepartureTime; ///< relevant times at which the action is timetabled, zeroed on creation so change
13840 TNumList ExitList; ///< the list of valid train exit TrackVector positions for 'Fer' entries (empty to begin with)
13841 TTimetableFormatType FormatType; ///< defines the timetable action type
13842 TTimetableLocationType LocationType; ///< indicates where the train is when the relevant action occurs
13843 TTimetableSequenceType SequenceType; ///< indicates where in the sequence of codes the action lies
13844 TTimetableShuttleLinkType ShuttleLinkType; ///< indicates whether or not the action relates to a shuttle service link
13845 TTrainDataEntry *LinkedTrainEntryPtr; ///< link pointer for use between fsp/rsp & Sfs; Fjo & jbo; Fns & Sns; & all shuttle to shuttle
13847 TTrainDataEntry *NonRepeatingShuttleLinkEntryPtr; ///< pointer used by shuttles for the non-shuttle train links, in & out, the
13849 
13850 // inline function
13851 
13853 TActionVectorEntry() {
13854 RearStartOrRepeatMins=0; FrontStartOrRepeatDigits=0; NumberOfRepeats=0; FormatType=NoFormat;
13855 SequenceType=NoSequence; LocationType=NoLocation; ShuttleLinkType=NoShuttleLink, EventTime=TDateTime(-1);
13856 ArrivalTime=TDateTime(-1); DepartureTime=TDateTime(-1); LinkedTrainEntryPtr=0; NonRepeatingShuttleLinkEntryPtr=0;
13857 Warning = false; SignallerControl = false;
13858 }
13859 };
13860 
13861 typedef std::vector<TActionVectorEntry> TActionVector;//contains all actions for a single train
13862 
13863 class TTrainDataEntry //contains all data for a single train - copied into train object when becomes active
13864 {
13865 public:
13866 AnsiString HeadCode, ServiceReference, Description; ///< headcode is the first train's headcode, rest are calculated from repeat
13869 double MaxBrakeRate; ///< in metres/sec/sec
13870 double MaxRunningSpeed; ///< in km/h
13871 double PowerAtRail; ///< in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
13872 int Mass; ///< in kg
13873 int NumberOfTrains; ///< number of repeats + 1
13874 int SignallerSpeed; ///< in km/h for use when under signaller control
13875 int StartSpeed; ///< in km/h
13876 TActionVector ActionVector; ///< all the actions for the train
13877 TTrainOperatingDataVector TrainOperatingDataVector; ///< operating information for the train including all its repeats
13878 
13879 //inline function
13880 
13882 TTrainDataEntry()
13883 {
13884 StartSpeed=0; MaxRunningSpeed=0; NumberOfTrains=0;
13885 }
13886 };
13887 
13888 Allowable successors:-
13889 Snt unlocated -> Fer, TimeLoc (arr), TimeTimeLoc, (new) pas; No others
13890 Snt located -> No starts, no finishes except Frh, Fjo (as of v2.0.0), Fns, and F-nshs, no repeat, pas, TimeTimeLoc or TimeLoc arr;
13891 any other cmd or TimeLoc (dep) OK
13892 Snt-sh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK
13893 Sfs -> No starts, finishes except Frh & Fjo (as of v2.15.0), repeats, pas, TimeTimeLoc, TimeLoc arr, rsp, fsp; any other cmd or
13894 TimeLoc (dep) OK [must have departure & arrival before another split]
13895 Sns -> No starts, finishes except Frh & Fjo (as of v2.15.0), repeats, pas, TimeTimeLoc or TimeLoc arr; any other cmd or TimeLoc (dep) OK
13896 Sns-sh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK (must have a TimeLoc departure somewhere in
13897 sequence to
13898 set location, else fails)
13899 Sns-fsh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK (must have a TimeLoc departure somewhere in
13900 sequence to
13901 set location, else fails)
13902 Fns -> R only [must be preceded by a TimeLoc arrival at the finish location, not necessarily immediately]
13903 F-nshs -> Nothing (no repeats permitted)
13904 Fjo -> R only
13905 Frh -> R only
13906 Fer -> R only
13907 Frh-sh -> R only
13908 Fns-sh -> R only
13909 jbo -> No starts, repeats, pas, Fer or TimeTimeLoc; TimeLoc (dep), others OK [must be preceded by an event whose location is set]
13910 fsp -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13911 rsp -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13912 cdt -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13913 dsc -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13914 cms -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13915 TimeLoc (arr) -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK
13916 TimeLoc (dep) -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13917 TimeTimeLoc -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13918 (new) pas -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13919 Repeat -> Nothing
13920 
13921 There must be a TimeLoc arrival (or a Sns start at location) in a sequence so successive cmd locations can be set
13922 Check all Snt's & set Locations if located (located = zero start speed, either element at a location (but if rear element
13923 is a continuation then treated as unlocated), and location listed in the next TimeLoc entry, though needn't be immediately after)
13924 If Snt entry at a location specified in a following TimeLoc entry but start speed > 0 give error message
13925 Check all times increase or stay same through ActionVector
13926 Cycle through all entries in vector setting arr & dep times based on above list
13927 Add locations to all relevant cmd entries based on earlier arrival location (or earlier reference for Sfs & Sns)
13928 Check locations match the arr & dep TimeLoc entries
13929 Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs
13930 Make above valid succession checks
13931 Check all splits have matching Sfs headcodes (both ways), add locations to Sfs's & check times same [Sfs loc derived from preceding fsp/rsp
13932 loc]
13933 Check all new service headcodes (Sns) have matching headcodes (both ways), add locations to Sns's & check times same [Sns loc derived from
13934 preceding Fns loc]
13935 Check a split to 'x' doesn't again split to 'x' (anywhere, not just for one train, since headcodes can be duplicated)
13936 Check each Fns has matching Sns headcodes (both ways), add locations to Fns's & check times same
13937 Check all joins have matching headcodes (both ways), locations & times & don't occur in same sequence
13938 Check each joined by train not joined by same train again (anywhere, not just for one train, since headcodes can be duplicated)
13939 Set train info for Sfs & Sns entries
13940 Check each repeat entry exactly matches any included joins or splits (user has to enter it to show that really wants it)
13941 Check at least one platform long enough for a split (only need 2 lengths) & disallow if not, need length of 2 & 1 extra
13942 element at each end, or length of 3 & 1 extra element at either end
13943 Check all TimeLocs have either Arr or Dep times set and EventTime == -1
13944 Check all Cmds have EventTime set & Arr & Dep times = -1
13945 Check all locations except unlocated Snts, Fers and Repeats have a LocationName
13946 
13947 Give messages in function if errors detected and clear the vector. Return false for failure.
13948 */
13949 
13950 /* Earlier checks:-
13951 Checks carried out with error messages in this function:-
13952 At least one comma in the line (it's based on a csv file);
13953 No entries following train information;
13954 At least one comma in remainder after train information (i.e at least a start and a finish entry);
13955 SplitEntry returns false in an intermediate entry - message repeats the entry for information;
13956 First entry not a start entry;
13957 Train information incomplete before a start entry;
13958 Entry follows a finish entry but doesn't begin with 'R';
13959 SplitEntry returns false in a finish entry - message repeats the entry for information;
13960 Last action entry isn't a finish entry.
13961 
13962 Function returns false with no message if:-
13963 Timetable start time invalid (no message is given for an invalid time as the line is assumed to be an irrelevant line; if no start
13964 time is found at all then an error message is given in the calling function);
13965 SplitTrainInfo returns false (message given in called function);
13966 SplitRepeat returns false (message given in called function).
13967 
13968 Double crosslink (shuttle) table: [OtherHeadCode, NonRepeatingShuttleLinkHeadCode, LinkedTrainEntryPtr, NonRepeatingShuttleLinkEntryPtr] <-- these for
13969 easier searching for this table
13970 
13971 Command Format OtherHead NonRepeating- LinkedTrain- NonRepeating- Decsription
13972  Code ShuttleLink- EntryPtr ShuttleLink
13973  HeadCode EntryPtr
13974 
13975 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
13976 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
13977 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
13978 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (fdr-shld be rtn)N (shld be fdr) Luckily NonRep link not needed
13979 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
13980 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
13981 
13982 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
13983 
13984 */
13985 {
13986  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SecondPassActions,");
13987  //TDateTime LastArrivalTime;
13988 // double MinDwellTime;
13989  if(TrainDataVector.empty())
13990  {
13991  SecondPassMessage(GiveMessages, "Error in timetable - there appear to be no train services in the timetable, it must contain at least one");
13992  TrainDataVector.clear();
13993  Utilities->CallLogPop(1832);
13994  return(false);
13995  }
13996 /* new preliminary checks for v0.2b without changing anything, carry each out separately:-
13997  1) must have at least one actionvector entry
13998  2) if first actionvector entry not SignallerControl then must have at least one more actionvector entry
13999  3) if first actionvector entry is SignallerControl then must have no more actionvector entries except a repeat
14000  4) first entry must be a start;
14001  4a) if first entry is Snt and second is a finish then it can't be Fns-sh or Frh-sh
14002  4b) if first entry is Sns or Sfs and second is a finish then it must be either Frh or Fjo
14003  4c) if first entry is Snt-sh, Sns-sh or Sns-fsh second can't be a finish
14004  5) a start must be the first entry;
14005  6) a repeat entry must be the last;
14006  7) for other than SignallerControl the last entry must be repeat or finish; if last entry is a repeat the last but one must be a finish;
14007  8) a finish entry must be the last or last but one, and if last but one the last must be a repeat
14008  Other successor errors will be caught later as all 'throws' changed to messages prior to the bulk of the sucessor checks
14009 */
14010 
14011  TwoLocationList.clear(); //empty the list to begin with, added at v2.9.1
14012  TwoLocationFlag = false; //added at v2.9.1
14013  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (1)
14014  {
14015  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14016  if(TrainDataVector.at(x).ActionVector.empty())
14017  {
14018  SecondPassMessage(GiveMessages, "Error in timetable - the following service has no listed events, there must be at least one: " + TDEntry.HeadCode);
14019  TrainDataVector.clear();
14020  Utilities->CallLogPop(1833);
14021  return(false);
14022  }
14023  }
14024  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (2)
14025  {
14026  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14027  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14028  if(!(AVEntry0.SignallerControl))
14029  {
14030  if(TrainDataVector.at(x).ActionVector.size() == 1)
14031  {
14032  SecondPassMessage(GiveMessages, "Error in timetable - service must have a start event and at least one other for: " + TDEntry.HeadCode);
14033  TrainDataVector.clear();
14034  Utilities->CallLogPop(1822);
14035  return(false);
14036  }
14037  }
14038  }
14039  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (3)
14040  {
14041  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14042  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14043  if(AVEntry0.SignallerControl)
14044  {
14045  if(TrainDataVector.at(x).ActionVector.size() > 2)
14046  {
14047  SecondPassMessage(GiveMessages,
14048  "Error in timetable - a signaller controlled service can have no more than one item (a repeat) after the start event, see: " +
14049  TDEntry.HeadCode);
14050  TrainDataVector.clear();
14051  Utilities->CallLogPop(1837);
14052  return(false);
14053  }
14054  if(TrainDataVector.at(x).ActionVector.size() > 1)
14055  {
14056  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14057  if(AVEntry1.FormatType != Repeat)
14058  {
14059  SecondPassMessage(GiveMessages,
14060  "Error in timetable - a signaller controlled service cannot have any other than a repeat after the start event, see: " + TDEntry.HeadCode);
14061  TrainDataVector.clear();
14062  Utilities->CallLogPop(1838);
14063  return(false);
14064  }
14065  }
14066  }
14067  }
14068  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (4)
14069  {
14070  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14071  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14072  if(AVEntry0.SequenceType != StartSequence)
14073  {
14074  SecondPassMessage(GiveMessages, "Error in timetable - the first event must be a start for: " + TDEntry.HeadCode);
14075  TrainDataVector.clear();
14076  Utilities->CallLogPop(1824);
14077  return(false);
14078  }
14079  if((AVEntry0.Command == "Snt") && !AVEntry0.SignallerControl) // (4a) sig control condition added so there is a second AVEntry
14080  // 4a added at v2.0.0. This is only a rough check, Fer only valid for an unlocated Snt
14081  // and others for a located Snt, but those checks done later
14082  {
14083  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1); // must be a second entry if first not signallercontrol
14084  if((AVEntry1.SequenceType == FinishSequence) && ((AVEntry1.Command == "Fns-sh") || (AVEntry1.Command == "Frh-sh")))
14085  {
14086  SecondPassMessage(GiveMessages, "Error in timetable - finish events Fns-sh and Frh-sh not permitted immediately after an Snt entry for: " +
14087  TDEntry.HeadCode); //these are the only AtLoc finishes not allowed
14088  TrainDataVector.clear();
14089  Utilities->CallLogPop(2046);
14090  return(false);
14091  }
14092  }
14093  if((AVEntry0.Command == "Sns") || (AVEntry0.Command == "Sfs")) // (4b)
14094  // 4b added at v2.15.0
14095  {
14096  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14097  if((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo"))
14098  {
14099  SecondPassMessage(GiveMessages, "Error in timetable - only 'Frh' or 'Fjo' finish events are permitted immediately after "
14100  "an 'Sns' or 'Sfs' event for: " + TDEntry.HeadCode + ". The program is unable to determine the "
14101  "location of any other type of finish.");
14102  TrainDataVector.clear();
14103  Utilities->CallLogPop(2580);
14104  return(false);
14105  }
14106  }
14107  if((AVEntry0.Command == "Snt-sh") || (AVEntry0.Command == "Sns-sh") || (AVEntry0.Command == "Sns-fsh")) // (4c)
14108  // 4c added at v2.15.0
14109  {
14110  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14111  if(AVEntry1.SequenceType == FinishSequence)
14112  {
14113  SecondPassMessage(GiveMessages, "Error in timetable - a finish event can't immediately follow an 'Snt-sh', 'Sns-sh' or 'Sns-fsh' "
14114  "event for: " + TDEntry.HeadCode);
14115  TrainDataVector.clear();
14116  Utilities->CallLogPop(2616);
14117  return(false);
14118  }
14119  }
14120  }
14121  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (5)
14122  {
14123  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14124  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14125  {
14126  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14127  if((AVEntry.SequenceType == StartSequence) && (y != 0))
14128  {
14129  SecondPassMessage(GiveMessages, "Error in timetable - a start event is present that is not the first event for: " + TDEntry.HeadCode);
14130  TrainDataVector.clear();
14131  Utilities->CallLogPop(1825);
14132  return(false);
14133  }
14134  }
14135  }
14136  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (6)
14137  {
14138  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14139  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14140  {
14141  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14142  if((AVEntry.FormatType == Repeat) && (y != (TrainDataVector.at(x).ActionVector.size() - 1)))
14143  {
14144  SecondPassMessage(GiveMessages, "Error in timetable - a repeat is present that is not the last item for: " + TDEntry.HeadCode);
14145  TrainDataVector.clear();
14146  Utilities->CallLogPop(1826);
14147  return(false);
14148  }
14149  }
14150  }
14151  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (7)
14152  {
14153  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14154  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14155  {
14156  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14157  if((y == 0) && AVEntry.SignallerControl)
14158  {
14159  break;
14160  }
14161  if(y == (TrainDataVector.at(x).ActionVector.size() - 1))
14162  {
14163  if((AVEntry.FormatType != Repeat) && (AVEntry.SequenceType != FinishSequence))
14164  {
14165  SecondPassMessage(GiveMessages, "Error in timetable - the last item must be either a finish event or a repeat for: " + TDEntry.HeadCode);
14166  TrainDataVector.clear();
14167  Utilities->CallLogPop(1827);
14168  return(false);
14169  }
14170  if(AVEntry.FormatType == Repeat)
14171  {
14172  const TActionVectorEntry &LastButOneAVEntry = TrainDataVector.at(x).ActionVector.at(y - 1);
14173  if(LastButOneAVEntry.SequenceType != FinishSequence)
14174  {
14175  SecondPassMessage(GiveMessages, "Error in timetable - the event immediately before the repeat must be a finish for: " + TDEntry.HeadCode);
14176  TrainDataVector.clear();
14177  Utilities->CallLogPop(1828);
14178  return(false);
14179  }
14180  }
14181  }
14182  }
14183  }
14184  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (8)
14185  {
14186  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14187  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14188  {
14189  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14190  if(AVEntry.SequenceType == FinishSequence)
14191  {
14192  if((y != (TrainDataVector.at(x).ActionVector.size() - 1)) && (y != (TrainDataVector.at(x).ActionVector.size() - 2)))
14193  {
14194  SecondPassMessage(GiveMessages, "Error in timetable - a finish event must be either the last or last but one for: " + TDEntry.HeadCode);
14195  TrainDataVector.clear();
14196  Utilities->CallLogPop(1829);
14197  return(false);
14198  }
14199  if(y == (TrainDataVector.at(x).ActionVector.size() - 2))
14200  {
14201  if(TrainDataVector.at(x).ActionVector.at(y + 1).FormatType != Repeat)
14202  {
14203  SecondPassMessage(GiveMessages, "Error in timetable - the only event that can follow a finish event is a repeat for: " + TDEntry.HeadCode);
14204  TrainDataVector.clear();
14205  Utilities->CallLogPop(1830);
14206  return(false);
14207  }
14208  }
14209  }
14210  }
14211  }
14212 
14213  // end of new preliminary checks
14214 
14215  // check start event successor validity
14216  // For Snt & Snt-sh set location if stopped, don't set any times yet
14217  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14218  {
14219  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14220  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14221  // use reference so can change internals where necessary
14222  if((AVEntry0.Command == "Snt") || (AVEntry0.Command == "Snt-sh"))
14223  {
14224  AnsiString LocationName = "";
14225  if(IsSNTEntryLocated(0, TDEntry, LocationName))
14226  // it is at a location
14227  {
14228  AVEntry0.LocationName = LocationName; //located Snt location name set
14229  AVEntry0.LocationType = AtLocation;
14230  // check successor validity for located Snt that isn't a SignallerControl entry
14231  if(!AVEntry0.SignallerControl)
14232  {
14233  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14234  // at least 2 entries present checked in integrity check so (1) valid
14235  if(!AtLocSuccessor(AVEntry1))
14236  {
14237  // Frh following Snt-sh will return false in location check, so no need to check here
14238  SecondPassMessage(GiveMessages, "Error in timetable - stopped 'Snt' or 'Snt-sh' followed by an illegal event for: " +
14239  TDEntry.HeadCode + ". The event isn't valid for a stationary train.");
14240  TrainDataVector.clear();
14241  Utilities->CallLogPop(523);
14242  return(false);
14243  }
14244  }
14245  }
14246  else // check not Snt-sh & carry out successor validity checks for unlocated Snt that isn't a SignallerControl entry
14247  {
14248  if(AVEntry0.Command == "Snt-sh")
14249  {
14250  SecondPassMessage(GiveMessages, "Error in timetable - 'Snt-sh' event not at stop location for: " + TDEntry.HeadCode);
14251  TrainDataVector.clear();
14252  Utilities->CallLogPop(1042);
14253  return(false);
14254  }
14255  AVEntry0.LocationType = EnRoute;
14256  if(!AVEntry0.SignallerControl)
14257  {
14258  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14259  // at least 2 entries checked in integrity check so (1) valid
14260  if(!MovingSuccessor(AVEntry1))
14261  {
14262  SecondPassMessage(GiveMessages, "Error in timetable - unlocated 'Snt' not followed by 'Fer', 'pas' or an arrival for: " +
14263  TDEntry.HeadCode);
14264  TrainDataVector.clear();
14265  Utilities->CallLogPop(790);
14266  return(false);
14267  }
14268  }
14269  }
14270  }
14271  // check other start successors, all AtLoc
14272  else if(AVEntry0.SequenceType == StartSequence)
14273  {
14274  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
14275  // at least 2 entries present checked in integrity check so (1) valid
14276  if(!AtLocSuccessor(AVEntry1))
14277  {
14278  SecondPassMessage(GiveMessages, "Error in timetable - 'Sfs', 'Sns', 'Sns-sh', 'Snt-fsh' or 'Sns-fsh' followed by an illegal event for: " +
14279  TDEntry.HeadCode + ". The event isn't valid for a stationary train.");
14280  TrainDataVector.clear();
14281  Utilities->CallLogPop(793);
14282  return(false);
14283  }
14284  }
14285  }
14286 
14287 
14288  // set Sns-sh & Sns-fsh locations same as following TimeLoc departure entry location, if no departure before end of sequence give error message
14289  for(unsigned int x = 0; x < TrainDataVector.size(); x++) //at v2.15.0 set Sfs & Sns locations from corresponding fsp/rsp & Fns entries, shuttles ok as they are
14290  {
14291  bool FoundFlag = false;
14292  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14293  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14294  // use reference so can change internals
14295  if((AVEntry0.Command == "Sns-sh") || (AVEntry0.Command == "Sns-fsh"))
14296  {
14297  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size(); y++)
14298  {
14299  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y);
14300  if(AVEntry2.FormatType == TimeLoc)
14301  {
14302  FoundFlag = true;
14303  AVEntry0.LocationName = AVEntry2.LocationName; //Sns-sh & Sns-fsh location names set
14304  break;
14305  }
14306  }
14307  if(!FoundFlag)
14308  {
14309  SecondPassMessage(GiveMessages, "Error in timetable - no location departure following an 'Sns-sh' or 'Sns-fsh' event for: " + TDEntry.HeadCode);
14310  TrainDataVector.clear();
14311  Utilities->CallLogPop(851);
14312  return(false);
14313  }
14314  }
14315  }
14316 
14317 //carry out preliminary check on service ref linkages without setting any data - added at v2.15.0 as can be location errors if linked trains not present
14318 //first check for duplicates then linkages (also checked later but leave that in)
14319  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14320  {
14321  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14322  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14323  {
14324  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14325  if(AVEntry.OtherHeadCode != "")
14326  {
14327  if(!CheckForDuplicateCrossReferences(2, TDEntry.HeadCode, AVEntry.OtherHeadCode, GiveMessages))
14328  {
14329  Utilities->CallLogPop(2610);
14330  return(false); // error message given in called function
14331  }
14332  }
14333  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
14334  {
14335  if(!CheckForDuplicateCrossReferences(3, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, GiveMessages))
14336  {
14337  Utilities->CallLogPop(2611);
14338  return(false); // error message given in called function
14339  }
14340  }
14341  }
14342  }
14343 //cross reference check
14344  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14345  {
14346  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14347  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14348  {
14349  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14350  if((AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
14351  {
14352  if(AVEntry.OtherHeadCode != "")
14353  {
14354  if(!CheckCrossReferencesAndSetData(2, TDEntry.HeadCode, AVEntry.OtherHeadCode, false, false, GiveMessages))
14355  // false = non-shuttle
14356  {
14357  Utilities->CallLogPop(2612);
14358  return(false); // error message given in called function
14359  }
14360  }
14361  }
14362  }
14363  }
14364 
14365 // now repeat the check just for the shuttles
14366  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14367  {
14368  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14369  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14370  {
14371  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14372  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh"))
14373  {
14374  if(AVEntry.OtherHeadCode != "")
14375  {
14376  if(!CheckCrossReferencesAndSetData(3, TDEntry.HeadCode, AVEntry.OtherHeadCode, true, false, GiveMessages))
14377  // true = shuttle
14378  {
14379  Utilities->CallLogPop(2613);
14380  return(false); // error message given in called function
14381  }
14382  }
14383  }
14384  }
14385  }
14386 
14387 // check for proper non-repeating link cross references and that they have no repeats & that times are consistent
14388  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14389  {
14390  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14391  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14392  {
14393  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14394  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
14395  {
14396  if(!CheckNonRepeatingShuttleLinksAndSetData(1, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, false, GiveMessages))
14397  {
14398  Utilities->CallLogPop(2614);
14399  return(false); // error message given in called function
14400  }
14401  }
14402  }
14403  }
14404 
14405 //at v2.15.0 we want to set Sns, Sfs location names, but to set Sns & Sfs first need the linked Fns & fsp/rsp to have locations set as they aren't yet,
14406 //and before v2.15.0 they were set from the corresponding Sns & Sfs locations, which in turn were set from later TimeLoc departures. At v2.15.0 it
14407 //is required to have these commands followed by Frh & Fjo, so this is why we need the linked Fns & fsp/rsp to have locations set first. Now all Fns
14408 //will have a TimeLoc before, so that can provide its location, but fsp/rsp? Must they have a TimeLoc before? No, and can't rely on starting Sfs
14409 //having the location set yet. So, new restriction, insist on an rsp/fsp having a TimeLoc before it or a located Snt, and use one of those to set the
14410 //location for the rsp/fsp and hence the linked Sfs.
14411 
14412 //NB can't allow an Sfs to be followed by another split or won't find a name, test with many existing tts then add an error to find it
14413 //Fns must be preceded by an arrival
14414 
14415 //set name for Fns from earlier location name or fail if can't find
14416  bool LocFoundFlag, FnsFoundFlag;
14417  TActionVectorEntry *AVEntryFns;
14418  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14419  {
14420  LocFoundFlag = false;
14421  FnsFoundFlag = false;
14422  for(int y = TrainDataVector.at(x).ActionVector.size() - 1; y >= 0; y--) //search backwards
14423  {
14424  if(TrainDataVector.at(x).ActionVector.at(y).Command == "Fns")
14425  {
14426  AVEntryFns = &TrainDataVector.at(x).ActionVector.at(y);
14427  FnsFoundFlag = true;
14428  continue;
14429  }
14430  if(!FnsFoundFlag)
14431  {
14432  continue;
14433  }
14434  if(TrainDataVector.at(x).ActionVector.at(y).LocationName != "")
14435  {
14436  LocFoundFlag = true;
14437  AVEntryFns->LocationName = TrainDataVector.at(x).ActionVector.at(y).LocationName;
14438 // double EVT = double(AVEntryFns->EventTime); //test
14439  break; //name found
14440  }
14441  if(TrainDataVector.at(x).ActionVector.at(y).LocationType == AtLocation) //not named yet
14442  {
14443  continue;
14444  }
14445  else
14446  {
14447  SecondPassMessage(GiveMessages, "Error in timetable - the program can't determine the location of an 'Fns' finish, it must be preceded "
14448  "by an event at the same location that has an identified location name, normally an arrival, see "
14449  + TrainDataVector.at(x).ServiceReference);
14450  TrainDataVector.clear();
14451  Utilities->CallLogPop(2596);
14452  return(false);
14453  }
14454  }
14455  if(FnsFoundFlag && !LocFoundFlag)
14456  {
14457  SecondPassMessage(GiveMessages, "Error in timetable - the program can't determine the location of an 'Fns' finish, it must be preceded "
14458  "by an event at the same location that has an identified location name, normally an arrival, see "
14459  + TrainDataVector.at(x).ServiceReference);
14460  TrainDataVector.clear();
14461  Utilities->CallLogPop(2597);
14462  return(false);
14463  }
14464  }
14465 
14466 //now set all names for Sns entries from the above, new at v2.15.0
14467  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14468  {
14469  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14470  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14471  // use reference so can change internals
14472  if(AVEntry0.Command == "Sns")
14473  {
14474  //new at v2.15.0. Only set location if have a forward and backward linkage at same time (these all set when SecondPassActions called).
14475  //This isn't rigorous as may have more than one, but if do then will be caught below in CheckCrossReferencesAndSetData
14476  //If fail to find location just ignore as will be caught later in CheckCrossReferencesAndSetData
14477  //note that at this stage the OtherHeadCode values are service refs, as haven't yet been changed back to headcodes until
14478  //StripExcessFromHeadCode called at end of this function
14479  //need to be the same: forward & reverse service refs, event times, commands correspond
14480 
14481  //successor checks first: /no starts, /finishes except Frh & Fjo (as of v2.15.0), /repeats, /pas, /TimeTimeLoc or /TimeLoc arr; any other cmd or TimeLoc (dep) OK
14482  if(TDEntry.ActionVector.size() < 2)
14483  {
14484  SecondPassMessage(GiveMessages, "Error in timetable - insufficient actions follwing an 'Sns' event for: " + TDEntry.HeadCode);
14485  TrainDataVector.clear();
14486  Utilities->CallLogPop(2598);
14487  return(false);
14488  }
14489  TActionVectorEntry AVEntry1 = TDEntry.ActionVector.at(1);
14490  if(!AtLocSuccessor(AVEntry1))
14491  {
14492  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sns' event is followed by an illegal event for: " + TDEntry.HeadCode +
14493  ". The event isn't valid for a stationary train.");
14494  TrainDataVector.clear();
14495  Utilities->CallLogPop(2599);
14496  return(false);
14497  }
14498  if((AVEntry1.SequenceType == StartSequence) || ((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo")) ||
14499  (AVEntry1.FormatType == Repeat))
14500  {
14501  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sns' event is followed by an illegal event for: " + TDEntry.HeadCode);
14502  TrainDataVector.clear();
14503  Utilities->CallLogPop(2600);
14504  return(false);
14505  }
14506 
14507  //now set the location and location type
14508  TDateTime SnsEventTime = AVEntry0.EventTime;
14509 // double EVT = double(SnsEventTime); //test
14510  AnsiString SnsServiceRef = TDEntry.ServiceReference;
14511  bool BreakFlag = false;
14512  for(unsigned int y = 0; y < TrainDataVector.size(); y++)
14513  {
14514  for(unsigned int z = 0; z < TrainDataVector.at(y).ActionVector.size(); z++)
14515  {
14516  if((TrainDataVector.at(y).ActionVector.at(z).Command == "Fns") && (SnsEventTime == TrainDataVector.at(y).ActionVector.at(z).EventTime) &&
14517  (TrainDataVector.at(y).HeadCode == AVEntry0.OtherHeadCode))
14518  { //forward linkage found
14519  if(TrainDataVector.at(y).ActionVector.at(z).OtherHeadCode == SnsServiceRef) //OtherHeadCode values are service refs, see above
14520  { //reverse linkage found
14521  AVEntry0.LocationName = TrainDataVector.at(y).ActionVector.at(z).LocationName;
14522  AVEntry0.LocationType = AtLocation;
14523  BreakFlag = true;
14524  break;
14525  }
14526  }
14527  }
14528  if(BreakFlag)
14529  {
14530  break;
14531  }
14532  }
14533  //test for any unnamed AtLoc entries at end of name setting
14534  }
14535  }
14536 
14537 //trap errors where rsp/fsp follows an Sfs without a TimeLoc arrival before (or unlikely to be able to set fsp/rsp/Sfs location because Sfs locs set from linked fsp/rsp events)
14538  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14539  {
14540  const TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14541  if(AVEntry0.Command == "Sfs")
14542  {
14543  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size(); y++)
14544  {
14545  if(TrainDataVector.at(x).ActionVector.at(y).LocationName != "") //must be a timeloc as only they have loc set and are AtLoc (non-AtLoc trapped above)
14546  {
14547  break;
14548  }
14549  else if((TrainDataVector.at(x).ActionVector.at(y).Command == "fsp") || (TrainDataVector.at(x).ActionVector.at(y).Command == "rsp"))
14550  {
14551  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event must be followed by a departure and arrival before another split, see " + TrainDataVector.at(x).ServiceReference);
14552  TrainDataVector.clear();
14553  Utilities->CallLogPop(2586);
14554  return(false);
14555  }
14556  }
14557  }
14558  }
14559 
14560 //now name fsp/rsp actions
14561  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14562  {
14563  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14564  {
14565  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14566  if(AVEntry.LocationName != "")
14567  {
14568  for(unsigned int z = y + 1; z < TrainDataVector.at(x).ActionVector.size(); z++)
14569  {
14570  TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(z);
14571  // use reference so can change internals where necessary
14572  if((AVEntry2.Command == "fsp") || (AVEntry2.Command == "rsp"))
14573  {
14574  AVEntry2.LocationName = AVEntry.LocationName;
14575  } //test for any unnamed AtLoc entries at end of name setting
14576  else if(AVEntry2.LocationType != AtLocation)
14577  {
14578  break;
14579  }
14580  }
14581  }
14582  }
14583  }
14584 
14585 //check that all named or give error message
14586  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14587  {
14588  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14589  {
14590  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14591  if((AVEntry.Command == "fsp") || (AVEntry.Command == "rsp"))
14592  {
14593  if(AVEntry.LocationName == "")
14594  {
14595  SecondPassMessage(GiveMessages, "Error in timetable - an 'fsp' or 'rsp' event must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
14596  TrainDataVector.clear();
14597  Utilities->CallLogPop(2617);
14598  return(false);
14599  }
14600  }
14601  }
14602  }
14603 
14604 //now set all Sfs entries from the above
14605  for(unsigned int x = 0; x < TrainDataVector.size(); x++) //at v2.15.0 set Sfs & Sns locations from corresponding fsp/rsp & Fns entries, shuttles ok as they are
14606  {
14607  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14608  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14609  // use reference so can change internals
14610  if(AVEntry0.Command == "Sfs")
14611  {
14612  //new at v2.15.0. Only set location if have a forward and backward linkage at same time (these all set when SecondPassActions called). This isn't
14613  //rigorous as may have more than one, but if do then will be caught below in CheckCrossReferencesAndSetData
14614  //If fail to find location just ignore as will be caught later in CheckCrossReferencesAndSetData
14615  //note that at this stage the OtherHeadCode values are service refs, as haven't yet been changed back to headcodes until StripExcessFromHeadCode called
14616  //at end of this function
14617  //need to be the same: forward & reverse service refs, event times, commands correspond
14618 
14619  //successor checks first: /no starts, /finishes except Frh & Fjo (as of v2.15.0), /repeats, /pas, /TimeTimeLoc or /TimeLoc arr; any other cmd or TimeLoc (dep) OK
14620  if(TDEntry.ActionVector.size() < 2)
14621  {
14622  SecondPassMessage(GiveMessages, "Error in timetable - insufficient actions follwing an 'Sfs' event for: " + TDEntry.HeadCode);
14623  TrainDataVector.clear();
14624  Utilities->CallLogPop(2587);
14625  return(false);
14626  }
14627  TActionVectorEntry AVEntry1 = TDEntry.ActionVector.at(1);
14628  if(!AtLocSuccessor(AVEntry1))
14629  {
14630  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event is followed by an illegal event for: " + TDEntry.HeadCode +
14631  ". The event isn't valid for a stationary train.");
14632  TrainDataVector.clear();
14633  Utilities->CallLogPop(2588);
14634  return(false);
14635  }
14636  if((AVEntry1.SequenceType == StartSequence) || ((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo")) ||
14637  (AVEntry1.FormatType == Repeat))
14638  {
14639  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event is followed by an illegal event for: " + TDEntry.HeadCode);
14640  TrainDataVector.clear();
14641  Utilities->CallLogPop(2589);
14642  return(false);
14643  }
14644 
14645  //now set the location and location type
14646  TDateTime SfsEventTime = AVEntry0.EventTime;
14647  AnsiString SfsServiceRef = TDEntry.ServiceReference;
14648  bool BreakFlag = false;
14649  for(unsigned int y = 0; y < TrainDataVector.size(); y++)
14650  {
14651  for(unsigned int z = 0; z < TrainDataVector.at(y).ActionVector.size(); z++)
14652  {
14653  if(((TrainDataVector.at(y).ActionVector.at(z).Command == "fsp") || (TrainDataVector.at(y).ActionVector.at(z).Command == "rsp")) &&
14654  (SfsEventTime == TrainDataVector.at(y).ActionVector.at(z).EventTime) && (TrainDataVector.at(y).HeadCode == AVEntry0.OtherHeadCode))
14655  { //forward linkage found
14656  if(TrainDataVector.at(y).ActionVector.at(z).OtherHeadCode == SfsServiceRef) //OtherHeadCode values are service refs, see above
14657  { //reverse linkage found
14658  AVEntry0.LocationName = TrainDataVector.at(y).ActionVector.at(z).LocationName;
14659  AVEntry0.LocationType = AtLocation;
14660  BreakFlag = true;
14661  break;
14662  }
14663  }
14664  }
14665  if(BreakFlag)
14666  {
14667  break;
14668  }
14669  } //test for any unnamed AtLoc entries at end of name setting
14670  }
14671  }
14672 
14673  // set all cmd locations based on earlier location name in TimeLoc arrival or Sfs/Sns/Sns-sh/Sns-fsh/located Snt/Snt-sh locations
14674  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14675  {
14676  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14677  {
14678  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14679  if(AVEntry.LocationName != "")
14680  {
14681  for(unsigned int z = y + 1; z < TrainDataVector.at(x).ActionVector.size(); z++)
14682  {
14683  TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(z);
14684  // use reference so can change internals where necessary
14685  if((AVEntry2.Command != "") && (AVEntry2.LocationType == AtLocation))
14686  {
14687  AVEntry2.LocationName = AVEntry.LocationName;
14688  } //test for any unnamed AtLoc entries at end of name setting
14689  else
14690  {
14691  break;
14692  }
14693  }
14694  }
14695  }
14696  }
14697  // all location names should now be set
14698 
14699 //now test for any unnamed AtLoc entries where Command != "" and give message if find any - shouldn't find any if above checks comprehensive
14700  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14701  {
14702  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14703  {
14704  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14705  if((AVEntry.LocationType == AtLocation) && (AVEntry.LocationName == "") && (AVEntry.Command != ""))
14706  {
14707  if((AVEntry.Command == "jbo") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") || (AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "cms"))
14708  {
14709  SecondPassMessage(GiveMessages, "Error in timetable - '" + AVEntry.Command + "' must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
14710  TrainDataVector.clear();
14711  Utilities->CallLogPop(2619);
14712  return(false);
14713  }
14714  else if((AVEntry.Command == "Sns") || (AVEntry.Command == "Sfs"))
14715  {
14716  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
14717  "Please make sure that the finish event of the service that links to this event is preceded by an "
14718  "event at the same location that has an identified location name, normally an arrival.");
14719  TrainDataVector.clear();
14720  Utilities->CallLogPop(2620);
14721  return(false);
14722  }
14723  else if(AVEntry.Command == "Snt-sh")
14724  {
14725  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
14726  "Please make sure that the service starts with zero speed and is at a named location.");
14727  TrainDataVector.clear();
14728  Utilities->CallLogPop(2623);
14729  return(false);
14730  }
14731  else if((AVEntry.Command == "Sns-fsh") || (AVEntry.Command == "Sns-sh"))
14732  {
14733  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
14734  "Please make sure that the event is followed (not necessarily immediately) by a departure.");
14735  TrainDataVector.clear();
14736  Utilities->CallLogPop(2622);
14737  return(false);
14738  }
14739  else if((AVEntry.Command == "Frh") || (AVEntry.Command == "Fns") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "F-nshs"))
14740  {
14741  SecondPassMessage(GiveMessages, "Error in timetable - '" + AVEntry.Command + "must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
14742  TrainDataVector.clear();
14743  Utilities->CallLogPop(2621);
14744  return(false);
14745  }
14746  }
14747  }
14748  }
14749 
14750  // check remaining successor validity except for TimeLoc arr & dep since those times not set yet
14751  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14752  {
14753  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14754  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14755  {
14756  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14757  if((AVEntry.SequenceType == FinishSequence) && (AVEntry.Command != "F-nshs"))
14758  {
14759  if(y < (TrainDataVector.at(x).ActionVector.size() - 1))
14760  // i.e at least one more, must be a repeat
14761  {
14762  if(TrainDataVector.at(x).ActionVector.at(y + 1).FormatType != Repeat)
14763  {
14764  SecondPassMessage(GiveMessages, "Error in timetable - only a repeat can follow a finish event for: " + TDEntry.HeadCode);
14765  TrainDataVector.clear();
14766  Utilities->CallLogPop(798);
14767  return(false);
14768  }
14769  }
14770  }
14771  if(AVEntry.Command == "F-nshs")
14772  {
14773  if(y != (TrainDataVector.at(x).ActionVector.size() - 1))
14774  // i.e has to be the last
14775  {
14776  SecondPassMessage(GiveMessages, "Error in timetable - F-nshs (shuttle link) must be the last event for: " + TDEntry.HeadCode);
14777  TrainDataVector.clear();
14778  Utilities->CallLogPop(1049);
14779  return(false);
14780  }
14781  }
14782  if(AVEntry.Command == "pas")
14783  {
14784  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14785  {
14786  SecondPassMessage(GiveMessages, "Error in timetable - a 'pas' can't be the last event for: " + TDEntry.HeadCode);
14787  TrainDataVector.clear();
14788  Utilities->CallLogPop(1518);
14789  return(false);
14790  }
14791  }
14792  if(AVEntry.Command == "jbo")
14793  {
14794  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14795  {
14796  SecondPassMessage(GiveMessages, "Error in timetable - a 'jbo' can't be the last event for: " + TDEntry.HeadCode);
14797  TrainDataVector.clear();
14798  Utilities->CallLogPop(800);
14799  return(false);
14800  }
14801  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14802  if(!AtLocSuccessor(AVEntry2))
14803  {
14804  SecondPassMessage(GiveMessages, "Error in timetable - a jbo event is followed by an illegal event for: " + TDEntry.HeadCode +
14805  ". The event isn't valid for a stationary train.");
14806  TrainDataVector.clear();
14807  Utilities->CallLogPop(801);
14808  return(false);
14809  }
14810  }
14811  if((AVEntry.Command == "fsp") || (AVEntry.Command == "rsp"))
14812  {
14813  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14814  {
14815  SecondPassMessage(GiveMessages, "Error in timetable - a train split can't be the last event for: " + TDEntry.HeadCode);
14816  TrainDataVector.clear();
14817  Utilities->CallLogPop(802);
14818  return(false);
14819  }
14820  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14821  if(!AtLocSuccessor(AVEntry2))
14822  {
14823  SecondPassMessage(GiveMessages, "Error in timetable - a train split is followed by an illegal event for: " + TDEntry.HeadCode +
14824  ". The event isn't valid for a stationary train.");
14825  TrainDataVector.clear();
14826  Utilities->CallLogPop(803);
14827  return(false);
14828  }
14829  }
14830  if(AVEntry.Command == "cdt")
14831  {
14832  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14833  {
14834  SecondPassMessage(GiveMessages, "Error in timetable - a 'cdt' can't be the last event for: " + TDEntry.HeadCode);
14835  TrainDataVector.clear();
14836  Utilities->CallLogPop(804);
14837  return(false);
14838  }
14839  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14840  if(!AtLocSuccessor(AVEntry2))
14841  {
14842  SecondPassMessage(GiveMessages, "Error in timetable - a 'cdt' is followed by an illegal event for: " + TDEntry.HeadCode +
14843  ". The event isn't valid for a stationary train.");
14844  TrainDataVector.clear();
14845  Utilities->CallLogPop(805);
14846  return(false);
14847  }
14848  }
14849  if(AVEntry.Command == "dsc")
14850  {
14851  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14852  {
14853  SecondPassMessage(GiveMessages, "Error in timetable - a 'dsc' can't be the last event for: " + TDEntry.HeadCode);
14854  TrainDataVector.clear();
14855  Utilities->CallLogPop(2602);
14856  return(false);
14857  }
14858  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14859  if(!AtLocSuccessor(AVEntry2))
14860  {
14861  SecondPassMessage(GiveMessages, "Error in timetable - a 'dsc' is followed by an illegal event for: " + TDEntry.HeadCode +
14862  ". The event isn't valid for a stationary train.");
14863  TrainDataVector.clear();
14864  Utilities->CallLogPop(2603);
14865  return(false);
14866  }
14867  }
14868  if(AVEntry.Command == "cms")
14869  {
14870  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14871  {
14872  SecondPassMessage(GiveMessages, "Error in timetable - a 'cms' can't be the last event for: " + TDEntry.HeadCode);
14873  TrainDataVector.clear();
14874  Utilities->CallLogPop(2711);
14875  return(false);
14876  }
14877  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14878  if(!AtLocSuccessor(AVEntry2))
14879  {
14880  SecondPassMessage(GiveMessages, "Error in timetable - a 'cms' is followed by an illegal event for: " + TDEntry.HeadCode +
14881  ". The event isn't valid for a stationary train.");
14882  TrainDataVector.clear();
14883  Utilities->CallLogPop(2712);
14884  return(false);
14885  }
14886  }
14887  if(AVEntry.FormatType == TimeTimeLoc)
14888  {
14889  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14890  {
14891  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure can't be the last event for: " + TDEntry.HeadCode);
14892  TrainDataVector.clear();
14893  Utilities->CallLogPop(806);
14894  return(false);
14895  }
14896  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14897  if(!MovingSuccessor(AVEntry2))
14898  {
14899  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure is followed by an illegal event for: " +
14900  TDEntry.HeadCode + ". The event isn't valid for a moving train.");
14901  TrainDataVector.clear();
14902  Utilities->CallLogPop(807);
14903  return(false);
14904  }
14905 /* dropped at v2.23.0 as too complex, and up to user to set timetable appropriately
14906 // if((AVEntry.DepartureTime - AVEntry.ArrivalTime) < TDateTime((AVEntry.MinDwellTime - 0.05) / 86400)) //subtract 0.05 to avoid rounding errors
14907  if((fabs(double(AVEntry.DepartureTime - AVEntry.ArrivalTime)) > (0.1 / 86400)) && ((AVEntry.DepartureTime - AVEntry.ArrivalTime) < TDateTime((AVEntry.MinDwellTime - 0.05) / 86400)))
14908  { //0.1 & 0.05 are to avoid rounding errors, fabs give the absolute value for doubles, compare with 0.1sec to avoid rounding errors
14909  SecondPassMessage(GiveMessages, "Error in timetable - the minimum dwell time is greater than the timetabled location stop duration, see " + TDEntry.HeadCode);
14910  TrainDataVector.clear();
14911  Utilities->CallLogPop(2740);
14912  return(false);
14913  }
14914  if((fabs(double(AVEntry.DepartureTime - AVEntry.ArrivalTime)) < (0.1 / 86400)) && (AVEntry.MinDwellTime > 30.05)) //i.e MDT of 30 ok for equal arr & dep times (30 is the default)
14915  { //0.1 & 0.05 are to avoid rounding errors, fabs give the absolute value for doubles, compare with 0.1sec to avoid rounding errors
14916  SecondPassMessage(GiveMessages, "Error in timetable - the minimum dwell time is greater than the timetabled location stop duration, see " + TDEntry.HeadCode);
14917  TrainDataVector.clear();
14918  Utilities->CallLogPop(2741);
14919  return(false);
14920  }
14921 */
14922  }
14923  if(AVEntry.FormatType == PassTime)
14924  {
14925  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14926  {
14927  SecondPassMessage(GiveMessages, "Error in timetable - a pass time can't be the last event for: " + TDEntry.HeadCode);
14928  TrainDataVector.clear();
14929  Utilities->CallLogPop(1530);
14930  return(false);
14931  }
14932  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14933  if(!MovingSuccessor(AVEntry2))
14934  {
14935  SecondPassMessage(GiveMessages, "Error in timetable - a pass time is followed by an illegal event for: " + TDEntry.HeadCode +
14936  ". The event isn't valid for a moving train.");
14937  TrainDataVector.clear();
14938  Utilities->CallLogPop(1531);
14939  return(false);
14940  }
14941  }
14942  if(AVEntry.FormatType == Repeat)
14943  {
14944  if(y != (TrainDataVector.at(x).ActionVector.size() - 1))
14945  {
14946  SecondPassMessage(GiveMessages, "Error in timetable - a repeat is not the last item for: " + TDEntry.HeadCode);
14947  TrainDataVector.clear();
14948  Utilities->CallLogPop(808);
14949  return(false);
14950  }
14951  }
14952  }
14953  }
14954 
14955  // set arrival & departure times for TimeLocs & set their EventTimes to -1
14956  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14957  {
14958  bool LastEntryIsAnArrival = false;
14959  const TTrainDataEntry & TDEntry = TrainDataVector.at(x);
14960  // first deal with unlocated Snt entries - so next entry (TimeLoc or TimeTimeLoc) is an arrival, all else stopped so the next TimeLoc is a departure
14961  const TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14962  if((AVEntry0.Command == "Snt") && (AVEntry0.LocationType == EnRoute))
14963  // StartSpeed may or may not be 0, but train will move forwards (if capable of doing so), & next TimeLoc will be an arrival, whether or not after one or more TimeTimeLocs
14964  {
14965  LastEntryIsAnArrival = false;
14966  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14967  {
14968  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14969  if(AVEntry.FormatType == TimeLoc)
14970  {
14971  if((AVEntry.ArrivalTime > TDateTime(-1)) || (AVEntry.DepartureTime > TDateTime(-1)) || (AVEntry.EventTime == TDateTime(-1)))
14972  {
14973  throw Exception("Timetable error, TimeLoc times not as initially set for " + TDEntry.HeadCode);
14974  }
14975  if(LastEntryIsAnArrival)
14976  {
14977  AVEntry.DepartureTime = AVEntry.EventTime;
14978  AVEntry.EventTime = TDateTime(-1);
14979  LastEntryIsAnArrival = false;
14980 // if((AVEntry.DepartureTime - LastArrivalTime) < TDateTime((MinDwellTime - 0.05) / 86400)) //subtract 0.05 to avoid rounding
14981 //double aa = double(LastArrivalTime); diagnostics
14982 //double bb = double(AVEntry.DepartureTime);
14983 //double cc = 0.1/86400;
14984 //double dd = double(AVEntry.ArrivalTime);
14985 /* dropped at v2.23.0 as too complex, and up to user to set timetable appropriately
14986  if((fabs(double(AVEntry.DepartureTime - LastArrivalTime)) > (0.1 / 86400)) && ((AVEntry.DepartureTime - LastArrivalTime) < TDateTime((MinDwellTime - 0.05) / 86400)))
14987  { //0.1 & 0.05 are to avoid rounding errors, fabs give the absolute value for doubles, compare with 0.1sec to avoid rounding errors
14988  SecondPassMessage(GiveMessages, "Error in timetable - the minimum dwell time is greater than the timetabled location stop duration, see " + TDEntry.HeadCode);
14989  TrainDataVector.clear();
14990  Utilities->CallLogPop(2745);
14991  return(false);
14992  }
14993  if((fabs(double(AVEntry.DepartureTime - LastArrivalTime)) < (0.1 / 86400)) && (MinDwellTime > 30.05)) //i.e MDT of 30 ok for equal arr & dep times (30 is the default)
14994  { //0.1 & 0.05 are to avoid rounding errors, fabs give the absolute value for doubles, compare with 0.1sec to avoid rounding errors
14995  SecondPassMessage(GiveMessages, "Error in timetable - the minimum dwell time is greater than the timetabled location stop duration, see " + TDEntry.HeadCode);
14996  TrainDataVector.clear();
14997  Utilities->CallLogPop(2746);
14998  return(false);
14999  }
15000 */
15001  if(AVEntry.MinDwellTime > 30.1)
15002  {
15003  SecondPassMessage(GiveMessages, "Error in timetable - a minimum dwell time is not permitted for a departure, "
15004  "add it to the corresponding arrival instead, see: " + TDEntry.HeadCode);
15005  TrainDataVector.clear();
15006  Utilities->CallLogPop(2742);
15007  return(false);
15008  }
15009  }
15010  else // last entry a departure
15011  {
15012  AVEntry.ArrivalTime = AVEntry.EventTime;
15013 // MinDwellTime = AVEntry.MinDwellTime;
15014 // LastArrivalTime = AVEntry.ArrivalTime;
15015  AVEntry.EventTime = TDateTime(-1);
15016  LastEntryIsAnArrival = true;
15017  }
15018  }
15019  }
15020  }
15021  else // all others stopped at beginning
15022  {
15023  LastEntryIsAnArrival = true;
15024  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15025  {
15026  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15027  if(AVEntry.FormatType == TimeLoc)
15028  {
15029  if((AVEntry.ArrivalTime > TDateTime(-1)) || (AVEntry.DepartureTime > TDateTime(-1)) || (AVEntry.EventTime == TDateTime(-1)))
15030  {
15031  throw Exception("Timetable error, TimeLoc times not as initially set for " + TDEntry.HeadCode);
15032  }
15033  if(LastEntryIsAnArrival)
15034  {
15035  AVEntry.DepartureTime = AVEntry.EventTime;
15036  AVEntry.EventTime = TDateTime(-1);
15037  LastEntryIsAnArrival = false;
15038  if(AVEntry.MinDwellTime > 30.1)
15039  {
15040  SecondPassMessage(GiveMessages, "Error in timetable - a minimum dwell time is not permitted for a departure, "
15041  "add it to the corresponding arrival instead, see: " + TDEntry.HeadCode);
15042  TrainDataVector.clear();
15043  Utilities->CallLogPop(2744);
15044  return(false);
15045  }
15046  }
15047  else // last entry a departure
15048  {
15049  AVEntry.ArrivalTime = AVEntry.EventTime;
15050  AVEntry.EventTime = TDateTime(-1);
15051  LastEntryIsAnArrival = true;
15052  }
15053  }
15054  }
15055  }
15056  }
15057  // perform remaining successor checks for TimeLocs
15058  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15059  {
15060  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15061  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15062  {
15063  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15064  if((AVEntry.FormatType == TimeLoc) && (AVEntry.ArrivalTime >= TDateTime(0))) // arrival
15065  // TimeLoc (arr) -> No starts, repeats, Fer or TimeTimeLoc; TimeLoc (dep) or any other OK
15066  {
15067  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
15068  {
15069  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival can't be the last event for: " + TDEntry.HeadCode);
15070  TrainDataVector.clear();
15071  Utilities->CallLogPop(809);
15072  return(false);
15073  }
15074  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
15075  if(!AtLocSuccessor(AVEntry2))
15076  {
15077  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival is followed by an illegal event for: " + TDEntry.HeadCode +
15078  ". The event isn't valid for a stationary train.");
15079  TrainDataVector.clear();
15080  Utilities->CallLogPop(810);
15081  return(false);
15082  }
15083  }
15084  if((AVEntry.FormatType == TimeLoc) && (AVEntry.DepartureTime >= TDateTime(0))) // departure
15085  // TimeLoc (dep) -> Fer, TimeLoc (arr), TimeTimeLoc, (new) pas OK, no others
15086  {
15087  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
15088  {
15089  SecondPassMessage(GiveMessages, "Error in timetable - a timed departure can't be the last event for: " + TDEntry.HeadCode);
15090  TrainDataVector.clear();
15091  Utilities->CallLogPop(811);
15092  return(false);
15093  }
15094  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
15095  if(!MovingSuccessor(AVEntry2))
15096  {
15097  SecondPassMessage(GiveMessages, "Error in timetable - a timed departure is followed by an illegal event for: " + TDEntry.HeadCode +
15098  ". The event isn't valid for a moving train.");
15099  TrainDataVector.clear();
15100  Utilities->CallLogPop(812);
15101  return(false);
15102  }
15103  }
15104  }
15105  }
15106 
15107  // check all TimeLocs have either Arr or Dep time set and EventTime == -1, all Cmds have EventTime set & Arr & Dep times == -1,
15108  // & repeats have no times set
15109  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15110  {
15111  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15112  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15113  {
15114  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15115  if(AVEntry.FormatType == TimeLoc)
15116  {
15117  if(AVEntry.EventTime != TDateTime(-1))
15118  {
15119  throw Exception("Timetable error, TimeLoc event has EventTime not -1 for " + TDEntry.HeadCode);
15120  }
15121  if((AVEntry.ArrivalTime == TDateTime(-1)) && (AVEntry.DepartureTime == TDateTime(-1)))
15122  {
15123  throw Exception("Timetable error, TimeLoc event has neither arrival nor departure time set for " + TDEntry.HeadCode);
15124  }
15125  }
15126  if(AVEntry.FormatType == TimeTimeLoc)
15127  {
15128  if(AVEntry.EventTime != TDateTime(-1))
15129  {
15130  throw Exception("Timetable error, TimeTimeLoc event has EventTime not -1 for " + TDEntry.HeadCode);
15131  }
15132  if((AVEntry.ArrivalTime == TDateTime(-1)) || (AVEntry.DepartureTime == TDateTime(-1)))
15133  {
15134  throw Exception("Timetable error, TimeTimeLoc event has either arrival or departure time not set for " + TDEntry.HeadCode);
15135  }
15136  }
15137  if((AVEntry.FormatType == TimeCmd) || (AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == StartNew) ||
15138  (AVEntry.FormatType == SNTShuttle) || (AVEntry.FormatType == SNSShuttle) || (AVEntry.FormatType == FNSNonRepeatToShuttle) ||
15139  (AVEntry.FormatType == FSHNewService) || (AVEntry.FormatType == PassTime))
15140  {
15141  if(AVEntry.EventTime == TDateTime(-1))
15142  {
15143  throw Exception("Timetable error, Cmd or PassTime event has EventTime not set for " + TDEntry.HeadCode);
15144  }
15145  if((AVEntry.ArrivalTime != TDateTime(-1)) || (AVEntry.DepartureTime != TDateTime(-1)))
15146  {
15147  throw Exception("Timetable error, Cmd or PassTime event has either arrival or departure time set for " + TDEntry.HeadCode);
15148  }
15149  }
15150  if(AVEntry.FormatType == Repeat)
15151  {
15152  if((AVEntry.EventTime != TDateTime(-1)) || (AVEntry.ArrivalTime != TDateTime(-1)) || (AVEntry.DepartureTime != TDateTime(-1)))
15153  {
15154  throw Exception("Timetable error, Repeat event has a time set for " + TDEntry.HeadCode);
15155  }
15156  }
15157  }
15158  }
15159 
15160  // check times stay same or increase, note that can have time of 0 if include midnight
15161  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15162  {
15163  TDateTime CurrentTime = TTClockTime; // the timetable start time
15164  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15165  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15166  {
15167  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15168  if(AVEntry.FormatType == Repeat)
15169  {
15170  break;
15171  }
15172  if(AVEntry.FormatType == FinRemHere)
15173  {
15174  break;
15175  }
15176  if(AVEntry.FormatType == TimeTimeLoc)
15177  {
15178  if(AVEntry.DepartureTime < AVEntry.ArrivalTime)
15179  {
15180  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure has a later arrival than departure time for: " +
15181  TDEntry.HeadCode);
15182  TrainDataVector.clear();
15183  Utilities->CallLogPop(813);
15184  return(false);
15185  }
15186  if(AVEntry.ArrivalTime < CurrentTime)
15187  {
15188  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure has too early an arrival time for: " +
15189  TDEntry.HeadCode);
15190  TrainDataVector.clear();
15191  Utilities->CallLogPop(814);
15192  return(false);
15193  }
15194  CurrentTime = AVEntry.DepartureTime;
15195  continue;
15196  }
15197  if(AVEntry.FormatType == TimeLoc)
15198  {
15199  if(AVEntry.ArrivalTime >= TDateTime(0))
15200  {
15201  if(AVEntry.ArrivalTime < CurrentTime)
15202  {
15203  SecondPassMessage(GiveMessages, "Error in timetable - a timed location event has a time that is too early for: " + TDEntry.HeadCode);
15204  TrainDataVector.clear();
15205  Utilities->CallLogPop(815);
15206  return(false);
15207  }
15208  CurrentTime = AVEntry.ArrivalTime;
15209  }
15210  else
15211  {
15212  if(AVEntry.DepartureTime < CurrentTime)
15213  // both may be 0 legitimately so must allow for this
15214  {
15215  SecondPassMessage(GiveMessages, "Error in timetable - a timed location event has a time that is too early for: " + TDEntry.HeadCode);
15216  TrainDataVector.clear();
15217  Utilities->CallLogPop(816);
15218  return(false);
15219  }
15220  CurrentTime = AVEntry.DepartureTime;
15221  }
15222  continue;
15223  }
15224  if(AVEntry.EventTime < CurrentTime)
15225  // all others have EventTime set
15226  {
15227  SecondPassMessage(GiveMessages, "Error in timetable - a train event has a time that is set too early for: " + TDEntry.HeadCode +
15228  ", may be before timetable start time");
15229  TrainDataVector.clear();
15230  Utilities->CallLogPop(835);
15231  return(false);
15232  }
15233  CurrentTime = AVEntry.EventTime;
15234  continue;
15235  }
15236  }
15237 
15238  // check locations consistent
15239  AnsiString LastLocationName = "";
15240 
15241  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15242  {
15243  bool LastEntryIsAnArrival = false;
15244  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15245  // first deal with moving Snt entries (all else stopped)
15246  if((TrainDataVector.at(x).ActionVector.at(0).Command == "Snt") && (TrainDataVector.at(x).ActionVector.at(0).LocationType == EnRoute))
15247  {
15248  LastEntryIsAnArrival = false;
15249  LastLocationName = TrainDataVector.at(x).ActionVector.at(0).LocationName; // should be ""
15250  if(LastLocationName != "")
15251  {
15252  throw Exception("Timetable error, moving Snt event has LocationName set for " + TDEntry.HeadCode);
15253  }
15254  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size();
15255  y++) // note that immediate successor to a moving Snt can only be a Moving type
15256  {
15257  // if it's a SignallerControl entry then the condition isn't met
15258  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15259  if(AVEntry.FormatType == Repeat)
15260  {
15261  break; // repeat = reached end (+allows repeat after signaller controlled entry)
15262  }
15263  else if((AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == FNSNonRepeatToShuttle))
15264  {
15265  if(AVEntry.LocationName != LastLocationName)
15266  {
15267  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
15268  AVEntry.Command);
15269  TrainDataVector.clear();
15270  Utilities->CallLogPop(823);
15271  return(false);
15272  }
15273  }
15274  else if((AVEntry.FormatType == TimeCmd) || (AVEntry.FormatType == TimeCmdDescription) || (AVEntry.FormatType == TimeCmdMaxSpeed))
15275  // cdt is the only TimeCmd, dsc is the only TimeCmdDescription & cms is the only TimeCmdMaxSpeed
15276  {
15277  if(AVEntry.LocationName != LastLocationName)
15278  {
15279  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
15280  AVEntry.Command);
15281  TrainDataVector.clear();
15282  Utilities->CallLogPop(824);
15283  return(false);
15284  }
15285  }
15286  else if(AVEntry.FormatType == TimeTimeLoc)
15287  {
15288  if((AVEntry.LocationName == LastLocationName) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
15289  // last entry must be a departure or would have failed earlier
15290  {
15291  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
15292  TwoLocationFlag = true;
15293 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
15294 // TwoOrMoreLocationsWarningGiven = true;
15295  }
15296  LastLocationName = AVEntry.LocationName;
15297  LastEntryIsAnArrival = false;
15298  }
15299  else if(AVEntry.FormatType == TimeLoc)
15300  {
15301  if(LastEntryIsAnArrival && (AVEntry.LocationName != LastLocationName))
15302  {
15303  SecondPassMessage(GiveMessages,
15304  "Error in timetable - a location event for a timed departure is different from the arrival location for: " + TDEntry.HeadCode);
15305  TrainDataVector.clear();
15306  Utilities->CallLogPop(826);
15307  return(false);
15308  }
15309  else if(!LastEntryIsAnArrival && (AVEntry.LocationName == LastLocationName))
15310  {
15311  SecondPassMessage(GiveMessages,
15312  "Error in timetable - a location event for a timed arrival is the same as the earlier departure location for: " + TDEntry.HeadCode);
15313  TrainDataVector.clear();
15314  Utilities->CallLogPop(827);
15315  return(false);
15316  }
15317  LastLocationName = AVEntry.LocationName;
15318  LastEntryIsAnArrival = !LastEntryIsAnArrival;
15319  }
15320  }
15321  }
15322  else // all stationary starting entries
15323  {
15324  LastEntryIsAnArrival = true;
15325  LastLocationName = TrainDataVector.at(x).ActionVector.at(0).LocationName;
15326  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15327  {
15328  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15329  if(AVEntry.FormatType == Repeat)
15330  {
15331  break;
15332  }
15333  else if((AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == FNSNonRepeatToShuttle))
15334  // no need to add anything for shuttle starts since they are at loc (0) anyway
15335  {
15336  if(AVEntry.LocationName != LastLocationName)
15337  {
15338  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
15339  AVEntry.Command);
15340  TrainDataVector.clear();
15341  Utilities->CallLogPop(828);
15342  return(false);
15343  }
15344  }
15345  else if(AVEntry.FormatType == TimeCmd)
15346  // cdt is the only TimeCmd
15347  {
15348  if(AVEntry.LocationName != LastLocationName)
15349  {
15350  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
15351  AVEntry.Command);
15352  TrainDataVector.clear();
15353  Utilities->CallLogPop(829);
15354  return(false);
15355  }
15356  }
15357  else if(AVEntry.FormatType == TimeTimeLoc)
15358  {
15359  if((AVEntry.LocationName == LastLocationName) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
15360  // last entry must be a departure or would have failed earlier
15361  {
15362  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
15363  TwoLocationFlag = true;
15364 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
15365 // TwoOrMoreLocationsWarningGiven = true;
15366  }
15367  LastLocationName = AVEntry.LocationName;
15368  LastEntryIsAnArrival = false;
15369  }
15370  else if(AVEntry.FormatType == TimeLoc)
15371  {
15372  if(LastEntryIsAnArrival && (AVEntry.LocationName != LastLocationName))
15373  {
15374  SecondPassMessage(GiveMessages,
15375  "Error in timetable - a location event for a timed departure is different from the arrival location for: " + TDEntry.HeadCode);
15376  TrainDataVector.clear();
15377  Utilities->CallLogPop(831);
15378  return(false);
15379  }
15380  if(!LastEntryIsAnArrival && (AVEntry.LocationName == LastLocationName) && !TwoOrMoreLocationsWarningGiven)
15381  {
15382  SecondPassMessage(GiveMessages,
15383  "A location event for a timed arrival is the same as the earlier departure location for: " + TDEntry.HeadCode + ". Please correct if this is an error.\n\nThis warning will not be shown again.");
15385 // TrainDataVector.clear();
15386 // Utilities->CallLogPop(832);
15387 // return false;
15388  }
15389  LastLocationName = AVEntry.LocationName;
15390  LastEntryIsAnArrival = !LastEntryIsAnArrival;
15391  }
15392  }
15393  }
15394  }
15395 
15396  // Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs (just a potential error warning given in v2.6.0)
15397  // i.e. same location can appear in any number of consecutive entries but once changed couldn't repeat before a direction change prior to v2.6.0
15398  AnsiString LocationNameToBeChecked = "";
15399 
15400  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15401  {
15402  const TTrainDataEntry & TDEntry = TrainDataVector.at(x);
15403  unsigned int y = 0;
15404  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
15405  // first discard unlocated Snt entries as they don't have location name set
15406  if((AVEntry0.Command == "Snt") && (AVEntry0.LocationType == EnRoute))
15407  {
15408  y = 1;
15409  }
15410  while(y < TDEntry.ActionVector.size())
15411  // need to check each location name separately in turn, skipped for SignallerControl entries
15412  {
15413  if((TDEntry.ActionVector.at(y).Command == "Fer") || (TDEntry.ActionVector.at(y).FormatType == Repeat))
15414  {
15415  break; // out of the 'while' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
15416  }
15417  LocationNameToBeChecked = TDEntry.ActionVector.at(y).LocationName;
15418  for(unsigned int z = y; z < TDEntry.ActionVector.size(); z++)
15419  {
15420  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
15421  if((AVEntry.Command == "Fer") || (AVEntry.FormatType == Repeat))
15422  {
15423  break; // out of the 'z' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
15424  }
15425  if(AVEntry.Command == "cdt")
15426  {
15427  break; // out of the 'z' loop since the check is only valid up to a change of direction
15428  }
15429  if(AVEntry.LocationName == LocationNameToBeChecked)
15430  {
15431  continue; // keep going while name same
15432  }
15433  if(AVEntry.LocationName != LocationNameToBeChecked)
15434  // if name different check forwards to see if repeats
15435  {
15436  for(unsigned int a = z; a < TDEntry.ActionVector.size(); a++)
15437  {
15438  if(TDEntry.ActionVector.at(a).Command == "cdt")
15439  {
15440  break; // out of the 'a' & 'z' loops since the check is only valid up to a change of direction
15441  }
15442  if((TDEntry.ActionVector.at(a).LocationName == LocationNameToBeChecked) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
15443  {
15444  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
15445  TwoLocationFlag = true;
15446 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
15447 // TwoOrMoreLocationsWarningGiven = true;
15448  }
15449  }
15450  break; // out of the 'z' loop since have checked 'a' as far as need to
15451  }
15452  }
15453  y++;
15454  }
15455  }
15456  if(TwoLocationFlag) //messages for this moved to InterfaceUnit - separate box listing potential errors and asking user to check
15457  {
15458  TwoLocationList.sort(); //need to sort first in alphabetical order to ensure all duplictes removed
15459  TwoLocationList.unique(); //remove duplicates
15460  }
15461 
15462  // check all locations except unlocated 'Snt' & 'Fer' have LocationName set
15463  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15464  {
15465  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15466  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15467  {
15468  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15469  if((AVEntry.LocationName == "") && (AVEntry.Command != "Snt") && (AVEntry.Command != "Fer") && (AVEntry.FormatType != Repeat))
15470  {
15471  throw Exception("Error, non- 'Snt', 'Fer' or Repeat event doesn't have a location name set for " + TDEntry.HeadCode);
15472  }
15473  AnsiString LocName = "";
15474  // dummy, only used so can call IsSNTEntryLocated
15475  if((AVEntry.Command == "Snt") && (IsSNTEntryLocated(1, TrainDataVector.at(x), LocName)))
15476  {
15477  if(AVEntry.LocationName == "")
15478  {
15479  throw Exception("Error, 'Snt' event at a stop location doesn't have a location name set for " + TDEntry.HeadCode);
15480  }
15481  }
15482  if((AVEntry.Command == "Snt") && !(IsSNTEntryLocated(2, TrainDataVector.at(x), LocName)))
15483  {
15484  if(AVEntry.LocationName != "")
15485  {
15486  throw Exception("Error, 'Snt' unlocated event has a location name set for " + TDEntry.HeadCode);
15487  }
15488  }
15489  }
15490  }
15491 
15492 /* Check a split to 'x' doesn't again split to 'x' (anywhere, not just for one train, since headcodes can be duplicated)
15493  Check each joined by train not joined by same train again (anywhere, not just for one train, since headcodes can be duplicated)
15494  Check each change of headcode not repeated anywhere else (anywhere, not just for one train, since headcodes can be duplicated)
15495 
15496  i.e. check everywhere where there is an 'OtherHeadCode' that it matches once only with its reference (both ways) + set
15497  the OtherHeadCodeStartingEntryPtr pointers where appropriate + train information for splits & new services
15498 
15499  BUT need to separate the shuttles from non-shuttles, because can have two trains reference each other in both forms,
15500  eg 2F44 Sns-sh ends in Fns to 2F45, & Sns 2F45 ends in Fns-sh to 2F44. Here 2F45 is the 'OtherHeadCode' for both
15501  Sns-sh & Fns in train 2F44, & 2F44 is the 'OtherHeadCode' for both Sns & Fns-sh in train 2F45.
15502 */
15503  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // new test to ensure no duplicate links at all, other checks ensure none for shuttles,
15504  {
15505  // non-shuttles & non-repeating links separately, but don't check that there isn't a
15506  // duplicate between a non-repeating shuttle and another - leave original tests in as
15507  // these also set the pointers
15508  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15509  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15510  {
15511  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15512  if(AVEntry.OtherHeadCode != "")
15513  {
15514  if(!CheckForDuplicateCrossReferences(0, TDEntry.HeadCode, AVEntry.OtherHeadCode, GiveMessages))
15515  {
15516  Utilities->CallLogPop(1584);
15517  return(false); // error message given in called function
15518  }
15519  }
15520  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
15521  {
15522  if(!CheckForDuplicateCrossReferences(1, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, GiveMessages))
15523  {
15524  Utilities->CallLogPop(1585);
15525  return(false); // error message given in called function
15526  }
15527  }
15528  }
15529  }
15530 
15531  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15532  {
15533  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15534  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15535  {
15536  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15537  if((AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15538  {
15539  if(AVEntry.OtherHeadCode != "")
15540  {
15541  if(!CheckCrossReferencesAndSetData(0, TDEntry.HeadCode, AVEntry.OtherHeadCode, false, true, GiveMessages))
15542  // false = non-shuttle
15543  {
15544  Utilities->CallLogPop(864);
15545  return(false); // error message given in called function
15546  }
15547  }
15548  }
15549  }
15550  }
15551 
15552  // now repeat the check just for the shuttles
15553  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15554  {
15555  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15556  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15557  {
15558  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15559  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh"))
15560  {
15561  if(AVEntry.OtherHeadCode != "")
15562  {
15563  if(!CheckCrossReferencesAndSetData(1, TDEntry.HeadCode, AVEntry.OtherHeadCode, true, true, GiveMessages))
15564  // true = shuttle
15565  {
15566  Utilities->CallLogPop(1100);
15567  return(false); // error message given in called function
15568  }
15569  }
15570  }
15571  }
15572  }
15573 
15574  // check for proper non-repeating link cross references and that they have no repeats & that times are consistent
15575  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15576  {
15577  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15578  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15579  {
15580  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15581  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
15582  {
15583  if(!CheckNonRepeatingShuttleLinksAndSetData(0, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, true, GiveMessages))
15584  {
15585  Utilities->CallLogPop(1060);
15586  return(false); // error message given in called function
15587  }
15588  }
15589  }
15590  }
15591 
15592  // check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
15593  // when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles,
15594  // don't ever need to and as designed would skip repeats
15595  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15596  {
15597  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15598  {
15599  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15600  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh"))
15601  {
15602  if(!CheckShuttleServiceIntegrity(0, &(TrainDataVector.at(x)), GiveMessages))
15603  {
15604  Utilities->CallLogPop(1090);
15605  return(false); // error message given in called function
15606  }
15607  }
15608  }
15609  }
15610 
15611  // check all entries have all types set to something
15612  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15613  {
15614  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15615  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15616  {
15617  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15618  if(AVEntry.FormatType == NoFormat)
15619  {
15620  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has FormatType unset for: " + TDEntry.HeadCode);
15621  }
15622  else if(AVEntry.SequenceType == NoSequence)
15623  {
15624  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has SequenceType unset for: " + TDEntry.HeadCode);
15625  }
15626  else if(AVEntry.LocationType == NoLocation)
15627  {
15628  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has LocationType unset for: " + TDEntry.HeadCode);
15629  }
15630  else if(AVEntry.ShuttleLinkType == NoShuttleLink)
15631  {
15632  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has ShuttleLinkType unset for: " + TDEntry.HeadCode);
15633  }
15634  }
15635  }
15636 
15637  // all OK if reach here, so set up the TrainOperatingDataVector (already has one entry) & NumberOfTrains
15638  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15639  {
15640  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
15641  // non-const reference so can alter content
15642  TTrainOperatingData TData;
15643  const TActionVectorEntry &LastAVEntry = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
15644  if(LastAVEntry.FormatType == Repeat) // check if a repeat
15645  {
15646 /*
15647  class TTrainOperatingData
15648  {
15649  public:
15650  int TrainID; - default, set at construction
15651  TActionEventType EventReported; used during operation
15652  TRunningEntry RunningEntry; - default, set at construction
15653  TTrainOperatingData() {TrainID = -1; EventReported= NoEvent; RunningEntry=NotStarted;} //constructor, values set to defaults
15654  };
15655 */
15656  TDEntry.NumberOfTrains = LastAVEntry.NumberOfRepeats + 1;
15657  for(int y = 1; y < TDEntry.NumberOfTrains; y++)
15658  {
15659  TDEntry.TrainOperatingDataVector.push_back(TData);
15660  }
15661  }
15662  else
15663  {
15664  TDEntry.NumberOfTrains = 1;
15665  }
15666  }
15667 
15668  // check that don't include any Continuation names
15669  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15670  {
15671  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15672  {
15673  AnsiString LocName = TrainDataVector.at(x).ActionVector.at(y).LocationName;
15674  AnsiString HC = TrainDataVector.at(x).HeadCode;
15675  if(LocName != "")
15676  {
15677  if(Track->ContinuationNameMap.find(LocName) != Track->ContinuationNameMap.end())
15678  {
15679  SecondPassMessage(GiveMessages, "Error in timetable - continuation names (" + LocName + ") must not be included, see service " + HC);
15680  TrainDataVector.clear();
15681  Utilities->CallLogPop(1578);
15682  return(false);
15683  }
15684  }
15685  }
15686  }
15687 
15688  // check that all repeat times below 96h
15689  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15690  {
15691  int NumRepeats = (TrainDataVector.at(x).NumberOfTrains) - 1;
15692  int IncMinutes = 0;
15693  if(TrainDataVector.at(x).ActionVector.at(TrainDataVector.at(x).ActionVector.size() - 1).FormatType == Repeat)
15694  {
15695  IncMinutes = TrainDataVector.at(x).ActionVector.at(TrainDataVector.at(x).ActionVector.size() - 1).RearStartOrRepeatMins;
15696  }
15697  else
15698  {
15699  continue; // basic times already checked in CheckTimeValidity
15700  }
15701  AnsiString HC = TrainDataVector.at(x).HeadCode;
15702  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15703  {
15704  if((double)TrainDataVector.at(x).ActionVector.at(y).EventTime > -1)
15705  {
15706  if(((double)GetRepeatTime(32, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
15707  {
15708  SecondPassMessage(GiveMessages, "Error in timetable - a repeat time exceeds 95h 59m, see service " + HC); // 3d 23h 59m = 3.9993055556
15709  TrainDataVector.clear();
15710  Utilities->CallLogPop(1818);
15711  return(false);
15712  }
15713  }
15714  if((double)TrainDataVector.at(x).ActionVector.at(y).ArrivalTime > -1)
15715  {
15716  if(((double)GetRepeatTime(33, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
15717  // 3d 23h 59m = 3.9993055556
15718  {
15719  SecondPassMessage(GiveMessages, "Error in timetable - a repeat event time exceeds 95h 59m, see service " + HC);
15720  TrainDataVector.clear();
15721  Utilities->CallLogPop(1819);
15722  return(false);
15723  }
15724  }
15725  if((double)TrainDataVector.at(x).ActionVector.at(y).DepartureTime > -1)
15726  {
15727  if(((double)GetRepeatTime(34, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
15728  // 3d 23h 59m = 3.9993055556
15729  {
15730  SecondPassMessage(GiveMessages, "Error in timetable - a repeat event time exceeds 95h 59m, see service " + HC);
15731  TrainDataVector.clear();
15732  Utilities->CallLogPop(1820);
15733  return(false);
15734  }
15735  }
15736  }
15737  }
15738 
15739  // Now that all set up change any extended headcodes back to ordinary headcodes
15740  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15741  {
15742  StripExcessFromHeadCode(0, TrainDataVector.at(x).HeadCode);
15743  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15744  {
15745  StripExcessFromHeadCode(1, TrainDataVector.at(x).ActionVector.at(y).OtherHeadCode);
15746  StripExcessFromHeadCode(2, TrainDataVector.at(x).ActionVector.at(y).NonRepeatingShuttleLinkHeadCode);
15747  }
15748  }
15749 
15750  // SaveTrainDataVectorToFile(0);//for testing purposes
15752  Utilities->CallLogPop(782);
15753  return(true);
15754 }
15755 
15756 // ---------------------------------------------------------------------------
15757 // Moving successors: TimeLoc arr/TimeTimeLoc/pas/Fer; Only call this when moving so if TimeLoc found it must be a departure
15759 {
15760  return ((AVEntry.FormatType == TimeLoc) || (AVEntry.FormatType == TimeTimeLoc) || (AVEntry.Command == "pas") || (AVEntry.Command == "Fer"));
15761 }
15762 
15763 // ---------------------------------------------------------------------------
15764 // AtLoc successors: TimeLoc dep/jbo/fsp/rsp/cdt/dsc/cms/Frh/Fns/Fjo/Frh-sh/Fns-sh/F-nshs; Only call this when stationary so if TimeLoc found it must be an arrival
15766 {
15767  return ((AVEntry.FormatType == TimeLoc) || (AVEntry.Command == "jbo") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") ||
15768  (AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "Frh") || (AVEntry.Command == "Fns") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh-sh") ||
15769  (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "F-nshs") || (AVEntry.Command == "cms"));
15770 }
15771 
15772 // ---------------------------------------------------------------------------
15773 
15774 void TTrainController::StripExcessFromHeadCode(int Caller, AnsiString &HeadCode)
15775 {
15776  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StripExcessFromHeadCode," + HeadCode);
15777  if(HeadCode.Length() > 4) // ignore otherwise
15778  {
15779  HeadCode = HeadCode.SubString(HeadCode.Length() - 3, 4);
15780  }
15781  Utilities->CallLogPop(1593);
15782 }
15783 
15784 // ---------------------------------------------------------------------------
15785 
15786 bool TTrainController::CheckForDuplicateCrossReferences(int Caller, AnsiString MainHeadCode, AnsiString SecondHeadCode, bool GiveMessages)
15787 {
15788  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckForDuplicateCrossReferences," + MainHeadCode + "," +
15789  SecondHeadCode);
15790  int ForwardCount = 0;
15791  int ReverseCount = 0;
15792 
15793  if(MainHeadCode == SecondHeadCode)
15794  {
15795  SecondPassMessage(GiveMessages, "Error in timetable - Service " + MainHeadCode + " has an event that references itself");
15796  TrainDataVector.clear();
15797  Utilities->CallLogPop(1594);
15798  return(false);
15799  }
15800  // forward check
15801  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15802  {
15803  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15804  if(TDEntry.HeadCode == MainHeadCode)
15805  {
15806  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15807  {
15808  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15809  if(AVEntry.OtherHeadCode == SecondHeadCode)
15810  {
15811  ForwardCount++;
15812  }
15813  if(AVEntry.NonRepeatingShuttleLinkHeadCode == SecondHeadCode)
15814  // need own check in case both 'Other' & 'NonRepeating' have same headcode
15815  {
15816  ForwardCount++;
15817  }
15818  }
15819  }
15820  }
15821  if(ForwardCount == 0)
15822  // this is an exception because the headcodes are selected in the same order as the forward check
15823  {
15824  throw Exception("Error, ForwardCount == 0 in CheckForDuplicateCrossReferences after called with found values");
15825  }
15826  if(ForwardCount > 2)
15827  // can have 2 if one is Sns-sh linking from another leg of the shuttle, and Fns links out to that same leg
15828  {
15829  SecondPassMessage(GiveMessages, "Error in timetable - found more than two references to " + SecondHeadCode + " from a train whose headcode is " +
15830  MainHeadCode + ". Check the service cross references from each service, and check whether one or other service is listed twice or more.");
15831  TrainDataVector.clear();
15832  Utilities->CallLogPop(1587);
15833  return(false);
15834  }
15835  // reverse check
15836  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15837  {
15838  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15839  if(TDEntry.HeadCode == SecondHeadCode)
15840  {
15841  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15842  {
15843  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15844  if(AVEntry.OtherHeadCode == MainHeadCode)
15845  {
15846  ReverseCount++;
15847  }
15848  if(AVEntry.NonRepeatingShuttleLinkHeadCode == MainHeadCode)
15849  {
15850  ReverseCount++;
15851  }
15852  }
15853  }
15854  }
15855 
15856  if(ReverseCount == 0)
15857  {
15858  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + SecondHeadCode);
15859  TrainDataVector.clear();
15860  Utilities->CallLogPop(1588);
15861  return(false);
15862  }
15863  if(ReverseCount > 2)
15864  // can have 2 if one is a second shuttle leg with a link in from Fns, and it links out to the same service with Fxx-sh
15865  {
15866  SecondPassMessage(GiveMessages, "Error in timetable - found more than two references to " + MainHeadCode + " from a train whose headcode is " +
15867  SecondHeadCode + ". Check the service cross references from each service, and check whether one or other service is listed twice or more.");
15868  TrainDataVector.clear();
15869  Utilities->CallLogPop(1589);
15870  return(false);
15871  }
15872  if(ForwardCount != ReverseCount)
15873  {
15874  SecondPassMessage(GiveMessages, "Error in timetable - " + MainHeadCode + " has a different number of references to " + SecondHeadCode +
15875  " than the other way round");
15876  TrainDataVector.clear();
15877  Utilities->CallLogPop(1610);
15878  return(false);
15879  }
15880  Utilities->CallLogPop(1590);
15881  return(true);
15882 }
15883 
15884 // ---------------------------------------------------------------------------
15885 
15886 bool TTrainController::CheckCrossReferencesAndSetData(int Caller, AnsiString MainHeadCode, AnsiString OtherHeadCode, bool Shuttle, bool SetDataAndCheckLocations, bool GiveMessages)
15887 /* Return false for no find or more than one find, check correct types of link
15888  First run through all trains whose headcode is the MainHeadCode (may be > 1) & for each entry whose
15889  'other' is OtherHeadCode increment a forward counter. Keep a pointer to the 'OtherHeadCode' entry for use later
15890  Must be exactly 1 forward count. NB Forward relates to MainHeadCode
15891  Then do the same in reverse.
15892  Using the pointers check the event times, then check that the locations & commands match - if main is a split then other must be Sfs;
15893  if main is Fns other must be Sns; if main is jbo other must be Fjo.
15894  Also check platform lengths OK for a split location (call to Track function for this - at least one platform at location has to be long
15895  enough). If all succeeds so far set the relevant OtherHeadCodeStartingEntryPtr to the new service starting point + train information
15896  for Sfs & Sns services. Finally check the repeat entries if present are consistent
15897 
15898  Check all except the NonRepeatingShuttleLinkHeadCodes, which only occur from F-nshs to Sns-sh, and from Fns-sh to
15899  Sns-fsh. All others should check out OK, but check shuttles & non-shuttles separately.
15900 
15901  /NB prohibit main & other headcodes being same, causes probs in failing to recognise locations
15902 */
15903 
15904 {
15905  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckCrossReferencesAndSetData," + MainHeadCode + "," + OtherHeadCode);
15906  int ForwardCount = 0;
15907  int ReverseCount = 0;
15908  unsigned int ForwardTDVectorNumber, ReverseTDVectorNumber;
15909  TActionVectorEntry *ReverseEntryPtr = 0, *ForwardEntryPtr = 0;
15910  TTrainDataEntry *MainTrainDataPtr = 0;
15911  TTrainDataEntry *OtherTrainDataPtr = 0;
15912 
15913  // forward check
15914  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15915  {
15916  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15917  if(TDEntry.HeadCode == MainHeadCode)
15918  {
15919  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15920  {
15921  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15922  if(!Shuttle && (AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15923  {
15924  if(AVEntry.OtherHeadCode == OtherHeadCode)
15925  {
15926  MainTrainDataPtr = &TrainDataVector.at(x);
15927  ForwardEntryPtr = &AVEntry;
15928  ForwardCount++;
15929  ForwardTDVectorNumber = x;
15930  }
15931  }
15932  else if(Shuttle && ((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") ||
15933  (AVEntry.Command == "Frh-sh")))
15934  {
15935  if(AVEntry.OtherHeadCode == OtherHeadCode)
15936  {
15937  MainTrainDataPtr = &TrainDataVector.at(x);
15938  ForwardEntryPtr = &AVEntry;
15939  ForwardCount++;
15940  ForwardTDVectorNumber = x;
15941  }
15942  }
15943  }
15944  }
15945  }
15946  if(ForwardCount == 0)
15947  // this is an exception because the headcodes are selected in the same order as the forward check
15948  {
15949  throw Exception("Error, ForwardCount == 0 in CheckCrossReferencesAndSetData after called with found values");
15950  }
15951  if(ForwardCount > 1)
15952  {
15953  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + OtherHeadCode + " from a train whose headcode is " +
15954  MainHeadCode);
15955  TrainDataVector.clear();
15956  Utilities->CallLogPop(836);
15957  return(false);
15958  }
15959  // reverse check
15960  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15961  {
15962  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15963  if(TDEntry.HeadCode == OtherHeadCode)
15964  {
15965  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15966  {
15967  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15968  if(!Shuttle && (AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15969  {
15970  if(AVEntry.OtherHeadCode == MainHeadCode)
15971  {
15972  OtherTrainDataPtr = &TrainDataVector.at(x);
15973  ReverseCount++;
15974  ReverseEntryPtr = &AVEntry;
15975  ReverseTDVectorNumber = x;
15976  }
15977  }
15978  else if(Shuttle && ((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh")))
15979  {
15980  if(AVEntry.OtherHeadCode == MainHeadCode)
15981  {
15982  OtherTrainDataPtr = &TrainDataVector.at(x);
15983  ReverseCount++;
15984  ReverseEntryPtr = &AVEntry;
15985  ReverseTDVectorNumber = x;
15986  }
15987  }
15988  }
15989  }
15990  }
15991 
15992  if(ReverseCount == 0)
15993  {
15994  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + OtherHeadCode);
15995  TrainDataVector.clear();
15996  Utilities->CallLogPop(837);
15997  return(false);
15998  }
15999  if(ReverseCount > 1)
16000  {
16001  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + MainHeadCode + " from a train whose headcode is " +
16002  OtherHeadCode);
16003  TrainDataVector.clear();
16004  Utilities->CallLogPop(838);
16005  return(false);
16006  }
16007  // these will all be false for !Shuttle
16008  bool ForwardShuttleStart = ((ForwardEntryPtr->Command == "Sns-sh") || (ForwardEntryPtr->Command == "Snt-sh"));
16009  bool ForwardShuttleFinish = ((ForwardEntryPtr->Command == "Fns-sh") || (ForwardEntryPtr->Command == "Frh-sh"));
16010  bool ReverseShuttleStart = ((ReverseEntryPtr->Command == "Sns-sh") || (ReverseEntryPtr->Command == "Snt-sh"));
16011  bool ReverseShuttleFinish = ((ReverseEntryPtr->Command == "Fns-sh") || (ReverseEntryPtr->Command == "Frh-sh"));
16012 
16013  if(Shuttle && MainTrainDataPtr->ActionVector.back().FormatType != Repeat)
16014  {
16015  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + MainHeadCode + " does not have a repeat");
16016  TrainDataVector.clear();
16017  Utilities->CallLogPop(1058);
16018  return(false);
16019  }
16020  if(Shuttle && OtherTrainDataPtr->ActionVector.back().FormatType != Repeat)
16021  {
16022  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + OtherHeadCode + " does not have a repeat");
16023  TrainDataVector.clear();
16024  Utilities->CallLogPop(1059);
16025  return(false);
16026  }
16027  if(SetDataAndCheckLocations)
16028  {
16029  if(ForwardEntryPtr->LocationName == "")
16030  {
16031  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + OtherHeadCode +
16032  ". One or other service does not have a location set");
16033  TrainDataVector.clear();
16034  Utilities->CallLogPop(526);
16035  return(false);
16036  }
16037  if(ReverseEntryPtr->LocationName == "")
16038  {
16039  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + OtherHeadCode +
16040  ". One or other service does not have a location set");
16041  TrainDataVector.clear();
16042  Utilities->CallLogPop(527);
16043  return(false);
16044  }
16045  if(ForwardEntryPtr->LocationName != ReverseEntryPtr->LocationName)
16046  {
16047  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + OtherHeadCode +
16048  " is at a different location to the referencing train " + MainHeadCode);
16049  TrainDataVector.clear();
16050  Utilities->CallLogPop(842);
16051  return(false);
16052  }
16053  }
16054  // ignore shuttle repeat links for first time check
16055  if(!Shuttle)
16056  {
16057  if(ForwardEntryPtr->EventTime != ReverseEntryPtr->EventTime)
16058  {
16059  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + OtherHeadCode +
16060  " has a different event time to the referencing train " + MainHeadCode);
16061  TrainDataVector.clear();
16062  Utilities->CallLogPop(525);
16063  return(false);
16064  }
16065  }
16066  // need to allow for repeat times multiplying up by repeating time for shuttle repeat links
16067  // no need to check from reverse to forward as already checked links consistent, and if include will send message twice
16068  if(ForwardShuttleStart && ReverseShuttleFinish)
16069  // Shuttle must be true if these are true
16070  {
16071  if(!CheckShuttleRepeatTime(0, ForwardEntryPtr->EventTime, ReverseEntryPtr->EventTime, OtherTrainDataPtr->ActionVector.back().RearStartOrRepeatMins))
16072  {
16073  SecondPassMessage(GiveMessages, "Error in timetable - shuttle service " + MainHeadCode +
16074  " first repeat restart time not consistent with finish service " + OtherHeadCode);
16075  TrainDataVector.clear();
16076  Utilities->CallLogPop(1055);
16077  return(false);
16078  }
16079  }
16080  if((ReverseEntryPtr->Command == "Sfs") || (ReverseEntryPtr->Command == "Sns"))
16081  // doesn't matter about ForwardEntryPtr being Sfs/Sns as called for every occurrence of an 'OtherHeadCode' so won't escape
16082  {
16083  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
16084  {
16085  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' or 'Sns' event (" + OtherHeadCode +
16086  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
16087  TrainDataVector.clear();
16088  Utilities->CallLogPop(528);
16089  return(false);
16090  }
16091  }
16092  if(ReverseEntryPtr->Command == "Fjo")
16093  // doesn't matter about ForwardEntryPtr being Fjo as called for every occurrence of an 'OtherHeadCode' so won't escape
16094  {
16095  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
16096  {
16097  SecondPassMessage(GiveMessages, "Error in timetable - an 'Fjo' event (" + OtherHeadCode +
16098  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
16099  TrainDataVector.clear();
16100  Utilities->CallLogPop(862);
16101  return(false);
16102  }
16103  }
16104  if(ReverseEntryPtr->Command == "Fns")
16105  // doesn't matter about ForwardEntryPtr being Fns as called for every occurrence of an 'OtherHeadCode' so won't escape
16106  {
16107  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
16108  {
16109  SecondPassMessage(GiveMessages, "Error in timetable - an 'Fns' event (" + OtherHeadCode +
16110  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
16111  TrainDataVector.clear();
16112  Utilities->CallLogPop(529);
16113  return(false);
16114  }
16115  }
16116  if(ForwardEntryPtr->Command == "Sfs")
16117  {
16118  if((ReverseEntryPtr->Command != "fsp") && (ReverseEntryPtr->Command != "rsp"))
16119  {
16120  SecondPassMessage(GiveMessages,
16121  "Error in timetable - unable to find a corresponding split train event for the train that starts from a split whose headcode is " +
16122  MainHeadCode);
16123  TrainDataVector.clear();
16124  Utilities->CallLogPop(530);
16125  return(false);
16126  }
16127  }
16128  if((ForwardEntryPtr->Command == "fsp") || (ForwardEntryPtr->Command == "rsp"))
16129  {
16130  if(ReverseEntryPtr->Command != "Sfs")
16131  {
16132  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sfs' event for the train split whose headcode is " +
16133  MainHeadCode);
16134  TrainDataVector.clear();
16135  Utilities->CallLogPop(839);
16136  return(false);
16137  }
16138  else
16139  {
16140  if(SetDataAndCheckLocations)
16141  {
16142  if(!(Track->TimetabledLocationNameAllocated(4, ForwardEntryPtr->LocationName)))
16143  {
16144  SecondPassMessage(GiveMessages, "Error in timetable - can't find timetabled location '" + ForwardEntryPtr->LocationName + "' in railway - perhaps there are concourses without platforms?");
16145  TrainDataVector.clear();
16146  Utilities->CallLogPop(849);
16147  return(false);
16148  }
16149  if(!(Track->OneNamedLocationElementAtLocation(0, ForwardEntryPtr->LocationName)))
16150  {
16151  SecondPassMessage(GiveMessages, "Error in timetable - can't find any named location elements at '" + ForwardEntryPtr->LocationName + "' - perhaps there are concourses without platforms?");
16152  TrainDataVector.clear();
16153  Utilities->CallLogPop(850);
16154  return(false);
16155  }
16156 //determine whether LocationName is a station or non-station
16157  bool StationLocation = false;
16158  for(TTrack::TTrackVectorIterator TEIt = Track->InactiveTrackVector.begin(); TEIt != Track->InactiveTrackVector.end(); TEIt++)
16159  {
16160  if(TEIt->LocationName == ForwardEntryPtr->LocationName)
16161  {
16162  if(TEIt->TrackType != NamedNonStationLocation)
16163  {
16164  StationLocation = true;
16165  }
16166  }
16167  }
16168  if(StationLocation)
16169  {
16170  if(!(Track->OneStationLongEnoughForSplit(0, ForwardEntryPtr->LocationName)))
16171  {
16172  SecondPassMessage(GiveMessages, "Error in timetable - location too short to split a train at " + ForwardEntryPtr->LocationName);
16173  TrainDataVector.clear();
16174  Utilities->CallLogPop(846);
16175  return(false);
16176  }
16177  }
16178  else
16179  {
16180  if(!(Track->OneNonStationLongEnoughForSplit(0, ForwardEntryPtr->LocationName)))
16181  {
16182  SecondPassMessage(GiveMessages, "Error in timetable - location too short to split a train at " + ForwardEntryPtr->LocationName);
16183  TrainDataVector.clear();
16184  Utilities->CallLogPop(2660);
16185  return(false);
16186  }
16187  }
16188  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
16189  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16190  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
16191  {
16192  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
16193  }
16194  }
16195  // NB: May not be set if main train is a service continuation without a description, if so can't do much about it but doesn't affect operation, just the train information display
16196  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
16197  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16198  }
16199  }
16200  if(ForwardEntryPtr->Command == "Sns")
16201  {
16202  if(ReverseEntryPtr->Command != "Fns")
16203  {
16204  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Fns' event for the 'Sns' train whose headcode is " +
16205  MainHeadCode + " and is formed from a service with headcode " + OtherHeadCode);
16206  TrainDataVector.clear();
16207  Utilities->CallLogPop(531);
16208  return(false);
16209  }
16210  }
16211  if(ForwardEntryPtr->Command == "Fns")
16212  {
16213  if(ReverseEntryPtr->Command != "Sns")
16214  {
16215  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sns' event for the train whose headcode is " + MainHeadCode +
16216  " and forms a new service with headcode " + OtherHeadCode);
16217  TrainDataVector.clear();
16218  Utilities->CallLogPop(840);
16219  return(false);
16220  }
16221  else
16222  {
16223  if(SetDataAndCheckLocations)
16224  {
16225  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
16226  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16227  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
16228  {
16229  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
16230  }
16231  }
16232  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16233  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
16234  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16235  }
16236  }
16237  if(ForwardEntryPtr->Command == "jbo")
16238  {
16239  if(ReverseEntryPtr->Command != "Fjo")
16240  {
16241  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Fjo' event for the train whose headcode is " + MainHeadCode +
16242  " and is joined by a train with headcode " + OtherHeadCode);
16243  TrainDataVector.clear();
16244  Utilities->CallLogPop(841);
16245  return(false);
16246  }
16247  }
16248  if(ForwardEntryPtr->Command == "Fjo")
16249  {
16250  if(ReverseEntryPtr->Command != "jbo")
16251  {
16252  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'jbo' event for the train whose headcode is " + MainHeadCode +
16253  " and joins a train with headcode " + OtherHeadCode);
16254  TrainDataVector.clear();
16255  Utilities->CallLogPop(532);
16256  return(false);
16257  }
16258  else
16259  {
16260  if(SetDataAndCheckLocations)
16261  {
16262  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
16263  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16264  }
16265 /*
16266  if((MainTrainDataPtr->MaxRunningSpeed > 5) && (MainTrainDataPtr->MaxRunningSpeed < OtherTrainDataPtr->MaxRunningSpeed))
16267  {
16268  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed; //this should only be set when the trains join
16269  } not when internal timetable being compiled. Dropped at v2.15.0 when Brent Mackie reported error on 18/02/23 via discord
16270 
16271  //added test for > 5 [5 used instead of 0 because of possible floating point errors - though unlikely] above at v1.3.1 because the train will have a
16272  //zero MaxRunningSpeed if it continues from another service - its max speed is set when it takes over from the other service
16273  //notified of this problem by Ian Walker in his email of 25/03/13. Probably redundant anyway because the max speed is reduced at the changeover if the
16274  //'joined by' train's max speed is less.
16275 */
16276  }
16277  }
16278  if(ForwardShuttleStart)
16279  // (ForwardEntryPtr->Command == "Sns-sh") || (ForwardEntryPtr->Command == "Snt-sh"))
16280  {
16281  if(!ReverseShuttleFinish)
16282  // (ReverseEntryPtr->Command != "Fns-sh") && (ReverseEntryPtr->Command != "Frh-sh"))
16283  {
16284  SecondPassMessage(GiveMessages, "Error in timetable - incorrect shuttle link to train whose headcode is " + MainHeadCode +
16285  " from train whose headcode is " + OtherHeadCode + ", has to be Fns-sh, Frh-sh");
16286  TrainDataVector.clear();
16287  Utilities->CallLogPop(1056);
16288  return(false);
16289  }
16290  }
16291  if(ReverseShuttleStart)
16292  // (ReverseEntryPtr->Command == "Sns-sh") || (ReverseEntryPtr->Command == "Snt-sh"))
16293  {
16294  if(!ForwardShuttleFinish)
16295  // (ForwardEntryPtr->Command != "Fns-sh") && (ForwardEntryPtr->Command != "Frh-sh"))
16296  {
16297  SecondPassMessage(GiveMessages, "Error in timetable - incorrect shuttle link to train whose headcode is " + OtherHeadCode +
16298  " from train whose headcode is " + MainHeadCode + ", has to be Fns-sh, Frh-sh");
16299  TrainDataVector.clear();
16300  Utilities->CallLogPop(1057);
16301  return(false);
16302  }
16303  else
16304  {
16305  if(SetDataAndCheckLocations)
16306  {
16307  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
16308  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16309  }
16310 /* don't need LinkedTrainEntryPtr for 'OtherTrain' & don't need data transfer as this is done in the
16311  non-repeating link for Sns-sh & is provided at the outset for Snt-sh
16312 */
16313  }
16314  }
16315  // check repeat information consistent if present
16316  // note that won't be affected by the non-repeating shuttle links as these are in NonRepeatingShuttleLinkHeadCode
16317  // and those not accessed here
16318 
16319  // still need to check the non-repeating links and that they have no repeats - do that outside this function
16320  bool MainRepeat = false, OtherRepeat = false;
16321  TActionVectorEntry MainRepeatEntry, OtherRepeatEntry;
16322 
16323  if(MainTrainDataPtr->ActionVector.at(MainTrainDataPtr->ActionVector.size() - 1).FormatType == Repeat)
16324  {
16325  MainRepeat = true;
16326  MainRepeatEntry = MainTrainDataPtr->ActionVector.at(MainTrainDataPtr->ActionVector.size() - 1);
16327  }
16328  if(OtherTrainDataPtr->ActionVector.at(OtherTrainDataPtr->ActionVector.size() - 1).FormatType == Repeat)
16329  {
16330  OtherRepeat = true;
16331  OtherRepeatEntry = OtherTrainDataPtr->ActionVector.at(OtherTrainDataPtr->ActionVector.size() - 1);
16332  }
16333  if((MainRepeat && !OtherRepeat) || (!MainRepeat && OtherRepeat))
16334  {
16335  SecondPassMessage(GiveMessages, "Error in timetable - only one repeat is provided for the train whose headcode is " + MainHeadCode +
16336  " and the associated train with headcode " + OtherHeadCode);
16337  TrainDataVector.clear();
16338  Utilities->CallLogPop(844);
16339  return(false);
16340  }
16341  if(MainRepeat && OtherRepeat)
16342  {
16343  if((MainRepeatEntry.EventTime != OtherRepeatEntry.EventTime) || (MainRepeatEntry.RearStartOrRepeatMins != OtherRepeatEntry.RearStartOrRepeatMins) ||
16344  (MainRepeatEntry.FrontStartOrRepeatDigits != OtherRepeatEntry.FrontStartOrRepeatDigits) ||
16345  (MainRepeatEntry.NumberOfRepeats != OtherRepeatEntry.NumberOfRepeats))
16346  {
16347  SecondPassMessage(GiveMessages, "Error in timetable - repeat items don't correspond for the train whose headcode is " + MainHeadCode +
16348  " and the associated train with headcode " + OtherHeadCode);
16349  TrainDataVector.clear();
16350  Utilities->CallLogPop(845);
16351  return(false);
16352  }
16353  }
16354  Utilities->CallLogPop(863);
16355  return(true);
16356 }
16357 
16358 // ---------------------------------------------------------------------------
16359 
16360 void TTrainController::StripSpaces(int Caller, AnsiString &Input)
16361 // strip both leading and trailing spaces at ends of text and spaces before and after all commas and semicolons within the text
16362 {
16363  // strip spaces from extreme ends of input
16364  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StripSpaces," + AnsiString(Input));
16365  if(Input == "")
16366  {
16367  Utilities->CallLogPop(856);
16368  return;
16369  }
16370  while(Input[1] == ' ')
16371  {
16372  if(Input.Length() > 1)
16373  {
16374  Input = Input.SubString(2, Input.Length() - 1);
16375  }
16376  else
16377  {
16378  Input = "";
16379  Utilities->CallLogPop(857);
16380  return;
16381  }
16382  }
16383  if(Input == "")
16384  {
16385  Utilities->CallLogPop(858);
16386  return;
16387  }
16388  while(Input[Input.Length()] == ' ')
16389  {
16390  if(Input.Length() > 1)
16391  {
16392  Input = Input.SubString(1, Input.Length() - 1);
16393  }
16394  else
16395  {
16396  Input = "";
16397  Utilities->CallLogPop(859);
16398  return;
16399  }
16400  }
16401  // now strip spaces immediately after all commas and semicolons within the text
16402  AnsiString Output = "";
16403  bool DelimiterFound = false;
16404 
16405  for(int x = 1; x < Input.Length() + 1; x++)
16406  {
16407  if(DelimiterFound)
16408  {
16409  if(Input[x] == ' ')
16410  {
16411  continue;
16412  }
16413  }
16414  if((Input[x] != ',') && (Input[x] != ';'))
16415  {
16416  DelimiterFound = false;
16417  Output = Output + Input[x];
16418  }
16419  else
16420  {
16421  DelimiterFound = true;
16422  Output = Output + Input[x];
16423  }
16424  }
16425  if(Output == "")
16426  {
16427  Input = "";
16428  Utilities->CallLogPop(860);
16429  return;
16430  }
16431  // now strip spaces immediately before all commas and semicolons within the text
16432  Input = Output;
16433  Output = "";
16434  DelimiterFound = false;
16435  for(int x = Input.Length(); x > 0; x--)
16436  {
16437  if(DelimiterFound)
16438  {
16439  if(Input[x] == ' ')
16440  {
16441  continue;
16442  }
16443  }
16444  if((Input[x] != ',') && (Input[x] != ';'))
16445  {
16446  DelimiterFound = false;
16447  Output = AnsiString(Input[x]) + Output;
16448  }
16449  else
16450  {
16451  DelimiterFound = true;
16452  Output = AnsiString(Input[x]) + Output;
16453  }
16454  }
16455  Input = Output;
16456  Utilities->CallLogPop(861);
16457 }
16458 
16459 // ---------------------------------------------------------------------------
16460 
16461 bool TTrainController::IsSNTEntryLocated(int Caller, const TTrainDataEntry &TDEntry, AnsiString &LocationName) //this is the original version re-instated at v2.15.0 Beta6
16462 // checks if an Snt or Snt-sh entry with zero starting speed is followed (somewhere, not necessarily immediately) by a TimeLoc & has the same LocationName
16463 // and if so returns true. Also returns true for Snt, not Snt-sh, if at least 1 start element is a location & the entry is either
16464 // a signaller control entry & speed is zero or it is followed immediately by Frh, Fjo, Fns or F-nshs (allows empty stock pickup).
16465 // Always return false for entry at a continuation (may be named but not a stop location). Note that no successor validity checks
16466 // are done in this function, they must be done elsewhere.
16467 //a starting speed > 0 always returns false
16468 {
16469  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsSNTEntryLocated," + AnsiString(TDEntry.HeadCode));
16470  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
16471  LocationName = "";
16472  if(TDEntry.StartSpeed > 0)
16473  {
16474  Utilities->CallLogPop(1784);
16475  return(false);
16476  }
16477  if((AVEntry0.Command != "Snt") && (AVEntry0.Command != "Snt-sh"))
16478  {
16479  throw Exception("Error, first event not 'Snt' or 'Snt-sh' in IsSNTEntryLocated");
16480  }
16482  {
16483  Utilities->CallLogPop(852);
16484  return(false);
16485  }
16486  AnsiString LocRear = Track->TrackElementAt(507, AVEntry0.RearStartOrRepeatMins).ActiveTrackElementName;
16487  AnsiString LocFront = Track->TrackElementAt(508, AVEntry0.FrontStartOrRepeatDigits).ActiveTrackElementName;
16488 
16489  if(LocRear != "")
16490  {
16491  LocationName = LocRear;
16492  }
16493  else
16494  {
16495  LocationName = LocFront;
16496  }
16497  if(LocationName == "")
16498  {
16499  Utilities->CallLogPop(1036);
16500  return(false);
16501  }
16502  if(AVEntry0.SignallerControl)
16503  {
16504  Utilities->CallLogPop(1773);
16505  return(true);
16506  }
16507 // here if not a signaller start entry so must be at least one more entry, and it must be at the same location as the Snt to be located
16508 
16509 //Ok Not ok continue
16510 
16511 //Frh if Snt Frh-sh cdt
16512 //Fns if Snt Fns-sh fsp or rsp
16513 //Fjo if Snt TimeTimeLoc jbo
16514 //F-nshs if Snt pas dsc or cms
16515 //TimeLoc dep Fer
16516 // TimeLoc arr
16517 
16518  for(unsigned int y = 0; y < TDEntry.ActionVector.size(); y++)
16519  {
16520  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
16521  if(((AVEntry.Command == "Frh") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "F-nshs") || (AVEntry.Command == "Fns")) && (AVEntry0.Command == "Snt")) // added Fjo at v2.0.0 for empty stock
16522  {
16523  Utilities->CallLogPop(1037);
16524  return(true);
16525  }
16526  if((AVEntry.FormatType == TimeLoc) && (AVEntry.LocationName == LocationName)) //will be a departure if same name- times not set yet so can't use them to confirm
16527  {
16528  Utilities->CallLogPop(2442);
16529  return(true);
16530  }
16531  if((AVEntry.FormatType == TimeLoc) && (AVEntry.LocationName != LocationName)) //arrival, not located
16532  {
16533  Utilities->CallLogPop(2438);
16534  return(false);
16535  }
16536  if((AVEntry.Command == "Fer") || (AVEntry.Command == "pas") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh") || (AVEntry.FormatType == TimeTimeLoc))
16537  {
16538  Utilities->CallLogPop(854);
16539  return(false);
16540  }
16541  if((AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "cms") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") || (AVEntry.Command == "jbo"))
16542  {
16543  continue;
16544  }
16545  }
16546  Utilities->CallLogPop(855);
16547  return(false);
16548 
16549 }
16550 
16551 // ---------------------------------------------------------------------------
16552 
16553 bool TTrainController::CheckStartPositionValidity(int Caller, AnsiString RearElementStr, AnsiString FrontElementStr, bool GiveMessages)
16554 {
16555  // checks that the new train start elements are valid - both exist & are connected, and that not
16556  // attempting to start on a diverging leg (i.e. one segment on points & other on element connected to diverging leg) <--dropped at v2.18.0
16557  // & not starting with front on a continuation
16558  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckStartPositionValidity," + RearElementStr + "," + FrontElementStr);
16559  int RearPosition = 0, FrontPosition = 0; // RearExitPos = 0; //dropped at v2.18.0
16560 
16561  RearPosition = Track->GetTrackVectorPositionFromString(5, RearElementStr, GiveMessages);
16562  if(RearPosition < 0)
16563  // error message given in GetTrackVectorPositionFromString
16564  {
16565  Utilities->CallLogPop(759);
16566  return(false);
16567  }
16568  FrontPosition = Track->GetTrackVectorPositionFromString(6, FrontElementStr, GiveMessages);
16569  if(FrontPosition < 0)
16570  // error message given in GetTrackVectorPositionFromString
16571  {
16572  Utilities->CallLogPop(760);
16573  return(false);
16574  }
16575  TTrackElement RearTrackElement = Track->TrackElementAt(490, RearPosition);
16576  TTrackElement FrontTrackElement = Track->TrackElementAt(491, FrontPosition);
16577 // TTrackType RearType = RearTrackElement.TrackType; //dropped at v2.18.0
16578  TTrackType FrontType = FrontTrackElement.TrackType;
16579 
16580  // check front & rear connected
16581  for(int x = 0; x < 4; x++)
16582  {
16583  if(RearTrackElement.Conn[x] == FrontPosition)
16584  {
16585 // RearExitPos = x; //dropped at v2.18.0
16586  break;
16587  }
16588  if(x == 3) //if it gets here & not already found then not connected
16589  {
16590  TimetableMessage(GiveMessages, "Front element: " + FrontTrackElement.ElementID + " not linked to rear element: " + RearTrackElement.ElementID);
16591  Utilities->CallLogPop(762);
16592  return(false);
16593  }
16594  }
16595  // check not starting with front on a continuation
16596  if(FrontType == Continuation)
16597  {
16598  TimetableMessage(GiveMessages, "Front of train attempting to start on a continuation at: " + FrontElementStr);
16599  Utilities->CallLogPop(937);
16600  return(false);
16601  }
16602  // check not starting on a level crossing
16603  if(Track->IsLCAtHV(43, FrontTrackElement.HLoc, FrontTrackElement.VLoc))
16604  {
16605  TimetableMessage(GiveMessages, "Train attempting to start on a level crossing at: " + FrontElementStr);
16606  Utilities->CallLogPop(1951);
16607  return(false);
16608  }
16609  if(Track->IsLCAtHV(44, RearTrackElement.HLoc, RearTrackElement.VLoc))
16610  {
16611  TimetableMessage(GiveMessages, "Train attempting to start on a level crossing at: " + RearElementStr);
16612  Utilities->CallLogPop(1952);
16613  return(false);
16614  }
16615  // check if trying to start on diverging leg of points - allowed at v2.18.0, checks done during operation
16616 /*
16617  if((RearType == Points) && (RearExitPos == 3))
16618  {
16619  TimetableMessage(GiveMessages, "Front of train attempting to start on element connected to diverging points at: " + RearElementStr);
16620  Utilities->CallLogPop(936);
16621  return(false);
16622  }
16623 
16624  if((FrontType == Points) && (RearTrackElement.ConnLinkPos[RearExitPos] == 3))
16625  {
16626  TimetableMessage(GiveMessages, "Rear of train attempting to start on element connected to diverging points at: " + FrontElementStr);
16627  Utilities->CallLogPop(1808);
16628  return(false);
16629  }
16630 */
16631  Utilities->CallLogPop(905);
16632  return(true);
16633 }
16634 
16635 // ---------------------------------------------------------------------------
16636 
16637 bool TTrainController::CheckStartAllowable(int Caller, int RearPosition, int RearExitPos, AnsiString HeadCode, bool ReportFlag, TActionEventType &EventType)
16638 // Rear & front element validity already checked in CheckStartPositionValidity
16639 // This checks for points in correct orientation, no train at start position and not starting on a locked route
16640 {
16641  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckStartAllowable," + AnsiString(RearPosition) + "," +
16642  AnsiString(RearExitPos));
16643  TTrackElement RearTrackElement = Track->TrackElementAt(517, RearPosition);
16644 
16645  if(RearTrackElement.TrackType == Continuation)
16646  {
16647  EventType = FailTrainEntry;
16648  }
16649  else
16650  {
16651  EventType = FailCreateTrain;
16652  }
16653  int FrontPosition = RearTrackElement.Conn[RearExitPos];
16654  TTrackElement FrontTrackElement = Track->TrackElementAt(798, FrontPosition);
16655  int FrontEntryPos = RearTrackElement.ConnLinkPos[RearExitPos];
16656  TTrackType RearType = RearTrackElement.TrackType;
16657  TTrackType FrontType = FrontTrackElement.TrackType;
16658  AnsiString RearName, FrontName;
16659 
16660  if(RearTrackElement.ActiveTrackElementName != "")
16661  {
16662  RearName = RearTrackElement.ActiveTrackElementName;
16663  }
16664  else
16665  {
16666  RearName = RearTrackElement.ElementID;
16667  }
16668  if(FrontTrackElement.ActiveTrackElementName != "")
16669  {
16670  FrontName = FrontTrackElement.ActiveTrackElementName;
16671  }
16672  else
16673  {
16674  FrontName = FrontTrackElement.ElementID;
16675  }
16676  TPrefDirElement PrefDirElement; // needed for next function but not used
16677  int LockedVectorNumber; // needed for next function but not used
16678 
16679  if(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(12, FrontPosition, FrontEntryPos, PrefDirElement, LockedVectorNumber))
16680  {
16681  if(ReportFlag)
16682  {
16683  if(EventType == FailCreateTrain)
16684  {
16685  EventType = FailCreateLockedRoute;
16686  }
16687  else
16688  {
16689  EventType = FailEnterLockedRoute;
16690  }
16691  LogActionError(47, HeadCode, "", EventType, FrontName);
16692  }
16693  Utilities->CallLogPop(940);
16694  return(false);
16695  }
16696  if(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(13, RearPosition, RearExitPos, PrefDirElement, LockedVectorNumber))
16697  {
16698  if(ReportFlag)
16699  {
16700  if(EventType == FailCreateTrain)
16701  {
16702  EventType = FailCreateLockedRoute;
16703  }
16704  else
16705  {
16706  EventType = FailEnterLockedRoute;
16707  }
16708  LogActionError(48, HeadCode, "", EventType, RearName);
16709  }
16710  Utilities->CallLogPop(1809);
16711  return(false);
16712  }
16713  if((RearType != Bridge) && (RearTrackElement.TrainIDOnElement > -1))
16714  {
16715  if(ReportFlag)
16716  {
16717  LogActionError(27, HeadCode, "", EventType, RearName);
16718  }
16719  Utilities->CallLogPop(1810);
16720  return(false);
16721  }
16722  if((FrontType != Bridge) && (FrontTrackElement.TrainIDOnElement > -1))
16723  {
16724  if(ReportFlag)
16725  {
16726  if(EventType == FailCreateTrain)
16727  {
16728  LogActionError(28, HeadCode, "", EventType, FrontName);
16729  }
16730  else
16731  {
16732  LogActionError(43, HeadCode, "", EventType, RearName);
16733  }
16734  }
16735  Utilities->CallLogPop(941);
16736  return(false);
16737  }
16738  if(RearType == Bridge)
16739  {
16740  if((RearExitPos > 1) && (RearTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
16741  {
16742  if(ReportFlag)
16743  {
16744  LogActionError(29, HeadCode, "", EventType, RearName);
16745  }
16746  Utilities->CallLogPop(942);
16747  return(false);
16748  }
16749  if((RearExitPos < 2) && (RearTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
16750  {
16751  if(ReportFlag)
16752  {
16753  LogActionError(30, HeadCode, "", EventType, RearName);
16754  }
16755  Utilities->CallLogPop(943);
16756  return(false);
16757  }
16758  }
16759  if(FrontType == Bridge)
16760  {
16761  if((FrontEntryPos > 1) && (FrontTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
16762  {
16763  if(ReportFlag)
16764  {
16765  if(EventType == FailCreateTrain)
16766  {
16767  LogActionError(31, HeadCode, "", EventType, FrontName);
16768  }
16769  else
16770  {
16771  LogActionError(44, HeadCode, "", EventType, RearName);
16772  }
16773  }
16774  Utilities->CallLogPop(944);
16775  return(false);
16776  }
16777  if((FrontEntryPos < 2) && (FrontTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
16778  {
16779  if(ReportFlag)
16780  {
16781  if(EventType == FailCreateTrain)
16782  {
16783  LogActionError(45, HeadCode, "", EventType, FrontName);
16784  }
16785  else
16786  {
16787  LogActionError(46, HeadCode, "", EventType, RearName);
16788  }
16789  }
16790  Utilities->CallLogPop(945);
16791  return(false);
16792  }
16793  }
16794  EventType = FailCreatePoints; //modified at v2.18.0 so only fails if starting positions conflict with point attribute
16795  if(RearType == Points)
16796  {
16797  if(((RearTrackElement.Attribute == 1) && (RearExitPos == 1)) || ((RearTrackElement.Attribute == 0) && (RearExitPos == 3)))
16798  {
16799  if(ReportFlag)
16800  {
16801  LogActionError(33, HeadCode, "", FailCreatePoints, RearName);
16802  StopTTClockMessage(157, HeadCode + " can't be created, points set wrongly at " + RearName);
16803  }
16804  Utilities->CallLogPop(933);
16805  return(false);
16806  }
16807  }
16808  if(FrontType == Points)
16809  {
16810  if(((FrontTrackElement.Attribute == 1) && (FrontEntryPos == 1)) || ((FrontTrackElement.Attribute == 0) && (FrontEntryPos == 3)))
16811  {
16812  if(ReportFlag)
16813  {
16814  LogActionError(34, HeadCode, "", FailCreatePoints, FrontName);
16815  StopTTClockMessage(158, HeadCode + " can't be created, points set wrongly at " + RearName);
16816  }
16817  Utilities->CallLogPop(934);
16818  return(false);
16819  }
16820  }
16821 
16822  //this section added at v2.9.1 to prevent entry for a train when there's a route set against it
16823  int HLoc = Track->TrackElementAt(1027, RearPosition).HLoc;
16824  int VLoc = Track->TrackElementAt(1028, RearPosition).VLoc;
16825  int ELink = Track->TrackElementAt(1029, RearPosition).Link[RearExitPos]; //if route entry corresponds to RearExitPos then it's set against the train
16826  int RouteNumber; //not used
16827  if(Track->TrackElementAt(1030, RearPosition).TrackType == Continuation)
16828  {
16829  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(8, HLoc, VLoc, ELink, RouteNumber))
16830  {
16831  EventType = FailEntryRouteSetAgainst;
16832  if(ReportFlag)
16833  {
16834  LogActionError(63, HeadCode, "", EventType, RearName);
16835  }
16836  Utilities->CallLogPop(2317);
16837  return(false);
16838  }
16839  }
16840  Utilities->CallLogPop(939);
16841  return(true);
16842 }
16843 
16844 // ---------------------------------------------------------------------------
16845 
16846 AnsiString TTrainController::GetRepeatHeadCode(int Caller, AnsiString BaseHeadCode, int RepeatNumber, int IncDigits)
16847 {
16848  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetRepeatHeadCode," + BaseHeadCode + "," + AnsiString(RepeatNumber) +
16849  "," + AnsiString(IncDigits));
16850  if(!Last2CharactersBothDigits(1, BaseHeadCode) && (IncDigits > 0))
16851  {
16852  throw Exception("Error, last 2 characters not both digits and IncDigits > 0 in GetRepeatHeadCode");
16853  }
16854  if(!Last2CharactersBothDigits(2, BaseHeadCode))
16855  {
16856  Utilities->CallLogPop(1893);
16857  return(BaseHeadCode);
16858  }
16859  int BaseDigits = BaseHeadCode.SubString(3, 2).ToInt();
16860  int NextRepeatDigits = BaseDigits + (IncDigits * RepeatNumber);
16861 
16862  while(NextRepeatDigits >= 100)
16863  {
16864  NextRepeatDigits -= 100; // rolls over after 99
16865  }
16866  AnsiString NextRepeatDigitsStr = AnsiString(NextRepeatDigits);
16867 
16868  if(NextRepeatDigitsStr.Length() < 2)
16869  {
16870  NextRepeatDigitsStr = AnsiString('0') + NextRepeatDigitsStr;
16871  }
16872  AnsiString NextRepeatHeadCode = BaseHeadCode.SubString(1, 2) + NextRepeatDigitsStr;
16873 
16874  Utilities->CallLogPop(1365);
16875  return(NextRepeatHeadCode);
16876 }
16877 
16878 // ---------------------------------------------------------------------------
16879 
16880 TDateTime TTrainController::GetRepeatTime(int Caller, TDateTime BasicTime, int RepeatNumber, int IncMinutes)
16881 {
16882  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetRepeatTime," + AnsiString(double(BasicTime)) + "," +
16883  AnsiString(RepeatNumber) + "," + AnsiString(IncMinutes));
16884  TDateTime NextRepeatTime = BasicTime + TDateTime(((double)(RepeatNumber * IncMinutes)) / 1440.0); // 1440 = no. of minutes in 24h
16885  Utilities->CallLogPop(1366);
16886  return(NextRepeatTime);
16887 }
16888 
16889 // ---------------------------------------------------------------------------
16890 
16891 bool TTrainController::CheckShuttleRepeatTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes)
16892 // For success the ForwardEventTime + repeat time should == ReverseEventTime (allow 10secs either way since converting to doubles)
16893 {
16894  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckShuttleRepeatTime," + AnsiString(double(ForwardEventTime)) + "," +
16895  AnsiString(double(ReverseEventTime)) + "," + AnsiString(RepeatMinutes));
16896  int ForwardSecs = int(double(ForwardEventTime) * 86400);
16897  int ReverseSecs = int(double(ReverseEventTime) * 86400);
16898  int RepeatSecs = RepeatMinutes * 60;
16899 
16900  if((ForwardSecs > (ReverseSecs - RepeatSecs + 10)) || (ForwardSecs < (ReverseSecs - RepeatSecs - 10)))
16901  {
16902  Utilities->CallLogPop(1367);
16903  return(false);
16904  }
16905  else
16906  {
16907  Utilities->CallLogPop(1368);
16908  return(true);
16909  }
16910 }
16911 
16912 // ---------------------------------------------------------------------------
16913 
16914 bool TTrainController::CheckNonRepeatingShuttleLinksAndSetData(int Caller, AnsiString MainHeadCode, AnsiString NonRepeatingHeadCode, bool SetDataAndCheckLocations, bool GiveMessages)
16915 // check for proper non-repeating link cross references and that they have no repeats & that times are consistent, set links if SetDataAndCheckLocations true
16916 
16917 /* Double crosslink (shuttle) table:
16918 Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
16919  Code ShuttleLink- EntryPtr ShuttleLink-
16920  HeadCode EntryPtr
16921 
16922 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
16923 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
16924 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
16925 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
16926 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
16927 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
16928 
16929 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
16930 */
16931 
16932 {
16933  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckNonRepeatingShuttleLinksAndSetData," + MainHeadCode + "," +
16934  NonRepeatingHeadCode);
16935  int ForwardCount = 0;
16936  int ReverseCount = 0;
16937  unsigned int ForwardTDVectorNumber, ReverseTDVectorNumber;
16938  TActionVectorEntry *ReverseEntryPtr = 0, *ForwardEntryPtr = 0;
16939  // Forward corresponds to Main, Reverse to Other
16940  TTrainDataEntry *MainTrainDataPtr = 0;
16941  TTrainDataEntry *OtherTrainDataPtr = 0;
16942 
16943  // forward check
16944  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16945  {
16946  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16947  if(TDEntry.HeadCode == MainHeadCode)
16948  {
16949  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16950  {
16951  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16952  if(AVEntry.NonRepeatingShuttleLinkHeadCode == NonRepeatingHeadCode)
16953  {
16954  MainTrainDataPtr = &TrainDataVector.at(x);
16955  ForwardEntryPtr = &AVEntry;
16956  ForwardCount++;
16957  ForwardTDVectorNumber = x;
16958  }
16959  }
16960  }
16961  }
16962  if(ForwardCount == 0)
16963  // this is an exception because the headcodes are selected in the same order as the forward check
16964  {
16965  throw Exception("Error, ForwardCount == 0 in CheckNonRepeatingShuttleLinksAndSetData after called with found values");
16966  }
16967  if(ForwardCount > 1)
16968  {
16969  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + NonRepeatingHeadCode + " from a train whose headcode is " +
16970  MainHeadCode);
16971  TrainDataVector.clear();
16972  Utilities->CallLogPop(1061);
16973  return(false);
16974  }
16975  // reverse check
16976  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16977  {
16978  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16979  if(TDEntry.HeadCode == NonRepeatingHeadCode)
16980  {
16981  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16982  {
16983  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16984  if(AVEntry.NonRepeatingShuttleLinkHeadCode == MainHeadCode)
16985  {
16986  OtherTrainDataPtr = &TrainDataVector.at(x);
16987  ReverseCount++;
16988  ReverseEntryPtr = &AVEntry;
16989  ReverseTDVectorNumber = x;
16990  }
16991  }
16992  }
16993  }
16994 
16995  if(ReverseCount == 0)
16996  {
16997  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + NonRepeatingHeadCode);
16998  TrainDataVector.clear();
16999  Utilities->CallLogPop(1062);
17000  return(false);
17001  }
17002  if(ReverseCount > 1)
17003  {
17004  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + MainHeadCode + " from a train whose headcode is " +
17005  NonRepeatingHeadCode);
17006  TrainDataVector.clear();
17007  Utilities->CallLogPop(1063);
17008  return(false);
17009  }
17010  if(((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh")) && (MainTrainDataPtr->ActionVector.back().FormatType == Repeat))
17011  {
17012  SecondPassMessage(GiveMessages, "Error in timetable - shuttle connecting train " + MainHeadCode + " shouldn't have a repeat");
17013  TrainDataVector.clear();
17014  Utilities->CallLogPop(1064);
17015  return(false);
17016  }
17017  if((ForwardEntryPtr->Command != "F-nshs") && (ForwardEntryPtr->Command != "Sns-fsh") && (MainTrainDataPtr->ActionVector.back().FormatType != Repeat))
17018  {
17019  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + MainHeadCode + " does not have a repeat item");
17020  TrainDataVector.clear();
17021  Utilities->CallLogPop(1065);
17022  return(false);
17023  }
17024  if(SetDataAndCheckLocations)
17025  {
17026  if(ForwardEntryPtr->LocationName == "")
17027  {
17028  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + NonRepeatingHeadCode +
17029  ". One or other service does not have a location set");
17030  TrainDataVector.clear();
17031  Utilities->CallLogPop(1066);
17032  return(false);
17033  }
17034  if(ReverseEntryPtr->LocationName == "")
17035  {
17036  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + NonRepeatingHeadCode +
17037  ". One or other service does not have a location set");
17038  TrainDataVector.clear();
17039  Utilities->CallLogPop(1067);
17040  return(false);
17041  }
17042  if(ForwardEntryPtr->LocationName != ReverseEntryPtr->LocationName)
17043  {
17044  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + NonRepeatingHeadCode +
17045  " is at a different location to the referencing train " + MainHeadCode);
17046  TrainDataVector.clear();
17047  Utilities->CallLogPop(1068);
17048  return(false);
17049  }
17050  }
17051  if(ForwardEntryPtr->Command == "F-nshs")
17052  // i.e. the non repeating link into the shuttle service
17053  {
17054  if(ForwardEntryPtr->EventTime != ReverseEntryPtr->EventTime)
17055  {
17056  SecondPassMessage(GiveMessages, "Error in timetable - shuttle in-link service " + MainHeadCode +
17057  " finish time not consistent with start time of shuttle service " + NonRepeatingHeadCode);
17058  TrainDataVector.clear();
17059  Utilities->CallLogPop(1069);
17060  return(false);
17061  }
17062  }
17063  if(ForwardEntryPtr->Command == "Fns-sh")
17064  // i.e. the non repeating link out from the shuttle service
17065  {
17066  if(!CheckNonRepeatingShuttleLinkTime(0, ForwardEntryPtr->EventTime, ReverseEntryPtr->EventTime,
17067  MainTrainDataPtr->ActionVector.back().RearStartOrRepeatMins, MainTrainDataPtr->ActionVector.back().NumberOfRepeats))
17068  {
17069  SecondPassMessage(GiveMessages, "Error in timetable - service " + NonRepeatingHeadCode + ", which links out from shuttle service " + MainHeadCode +
17070  ", has the wrong start time. It should correspond to the finish time of the last shuttle.");
17071  TrainDataVector.clear();
17072  Utilities->CallLogPop(1070);
17073  return(false);
17074  }
17075  }
17076  if((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh"))
17077  // i.e. a non repeating link to or from the shuttle service
17078  {
17079  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
17080  {
17081  SecondPassMessage(GiveMessages, "Error in timetable - the non repeating link service " + NonRepeatingHeadCode +
17082  " appears in the same sequence as the corresponding shuttle service " + MainHeadCode);
17083  TrainDataVector.clear();
17084  Utilities->CallLogPop(1071);
17085  return(false);
17086  }
17087  }
17088 /* it's allowed to have a different description
17089  if((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh"))//i.e. a non repeating link to or from the shuttle service
17090  {
17091  if((MainTrainDataPtr->Description != "") && (OtherTrainDataPtr->Description != "") && (MainTrainDataPtr->Description != OtherTrainDataPtr->Description))
17092  {
17093  SecondPassMessage(GiveMessages, "Error in timetable - the non repeating link service " + NonRepeatingHeadCode + " has a different description to the corresponding shuttle service " + MainHeadCode);
17094  TrainDataVector.clear();
17095  Utilities->CallLogPop(1072);
17096  return false;
17097  }
17098  }
17099 */
17100  if(ForwardEntryPtr->Command == "Sns-sh")
17101  {
17102  if(ReverseEntryPtr->Command != "F-nshs")
17103  {
17104  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'F-nshs' event for the 'Sns-sh' train whose headcode is " +
17105  MainHeadCode + " and is a new shuttle service formed from the service with headcode " + NonRepeatingHeadCode);
17106  TrainDataVector.clear();
17107  Utilities->CallLogPop(1073);
17108  return(false);
17109  }
17110  }
17111  if(ForwardEntryPtr->Command == "F-nshs")
17112  {
17113  if(ReverseEntryPtr->Command != "Sns-sh")
17114  {
17115  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sns-sh' event for the 'F-nshs' train whose headcode is " +
17116  MainHeadCode + " and forms a new shuttle service with headcode " + NonRepeatingHeadCode);
17117  TrainDataVector.clear();
17118  Utilities->CallLogPop(1074);
17119  return(false);
17120  }
17121  else
17122  {
17123  if(SetDataAndCheckLocations)
17124  {
17125  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
17126  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
17127  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
17128  {
17129  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
17130  }
17131  }
17132  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
17133  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
17134  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
17135  }
17136  }
17137  if(ForwardEntryPtr->Command == "Sns-fsh")
17138  {
17139  if(ReverseEntryPtr->Command != "Fns-sh")
17140  {
17141  SecondPassMessage(GiveMessages,
17142  "Error in timetable - unable to find a corresponding 'Fns-sh' event for the 'Sns-fsh' non-shuttle service whose headcode is " + MainHeadCode +
17143  " formed from a shuttle service with headcode " + NonRepeatingHeadCode);
17144  TrainDataVector.clear();
17145  Utilities->CallLogPop(1075);
17146  return(false);
17147  }
17148  }
17149  if(ForwardEntryPtr->Command == "Fns-sh")
17150  {
17151  if(ReverseEntryPtr->Command != "Sns-fsh")
17152  {
17153  SecondPassMessage(GiveMessages,
17154  "Error in timetable - unable to find a corresponding 'Sns-fsh' event for the 'Fns-sh' shuttle service whose headcode is " + MainHeadCode +
17155  " and forms a new non-shuttle service with headcode " + NonRepeatingHeadCode);
17156  TrainDataVector.clear();
17157  Utilities->CallLogPop(1076);
17158  return(false);
17159  }
17160  else
17161  {
17162  if(SetDataAndCheckLocations)
17163  {
17164  ForwardEntryPtr->NonRepeatingShuttleLinkEntryPtr = OtherTrainDataPtr;
17165  // links to the non-repeating non-shuttle linked service
17166  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
17167  // needed for creating formatted timetable
17168  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
17169  {
17170  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
17171  }
17172  }
17173  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
17174  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
17175  }
17176  }
17177  Utilities->CallLogPop(1077);
17178  return(true);
17179 }
17180 
17181 // ---------------------------------------------------------------------------
17182 
17183 bool TTrainController::CheckNonRepeatingShuttleLinkTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes, int RepeatNumber)
17184 // Forward train is the finish shuttle entry 'Fns-sh'.
17185 // The Reverse (new non-repeating service) time must == Forward time + (RepeatMins * RepeatNumber) but allow 10 secs either side
17186 {
17187  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckNonRepeatingShuttleLinkTime," + AnsiString(double(ForwardEventTime))
17188  + "," + AnsiString(double(ReverseEventTime)) + "," + AnsiString(RepeatMinutes) + "," + AnsiString(RepeatNumber));
17189  int ForwardSecs = int(double(ForwardEventTime) * 86400);
17190  int ReverseSecs = int(double(ReverseEventTime) * 86400);
17191  int RepeatSecs = RepeatMinutes * RepeatNumber * 60;
17192 
17193  if((ReverseSecs > (ForwardSecs + RepeatSecs + 10)) || (ReverseSecs < (ForwardSecs + RepeatSecs - 10)))
17194  {
17195  Utilities->CallLogPop(1369);
17196  return(false);
17197  }
17198  else
17199  {
17200  Utilities->CallLogPop(1370);
17201  return(true);
17202  }
17203 }
17204 
17205 // ---------------------------------------------------------------------------
17206 
17207 bool TTrainController::CheckShuttleServiceIntegrity(int Caller, TTrainDataEntry *TDEntryPtr, bool GiveMessages)
17208 // check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
17209 // when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles,
17210 // don't ever need to and as designed would skip repeats.
17211 
17212 // enter with TDEntry a shuttle start - Snt-sh or Sns-sh
17213 {
17214  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckShuttleServiceIntegrity," + AnsiString(TDEntryPtr->HeadCode));
17215  if(TDEntryPtr->ActionVector.back().FormatType != Repeat)
17216  {
17217  throw Exception("Error - last entry in " + TDEntryPtr->HeadCode + " service is not a repeat - should have already found this error");
17218  }
17219  TTrainDataEntry *ShuttleStartAddress = TDEntryPtr;
17220  AnsiString OriginalHeadCode = TDEntryPtr->HeadCode;
17221  AnsiString LastActionCommand = (TDEntryPtr->ActionVector.end() - 2)->Command;
17222 
17223  if((LastActionCommand != "Fns") && (LastActionCommand != "Fns-sh") && (LastActionCommand != "Frh-sh"))
17224  {
17225  SecondPassMessage(GiveMessages, "Error in timetable - last event in shuttle service " + TDEntryPtr->HeadCode + " is not 'Fns', 'Fns-sh' or 'Frh-sh'");
17226  TrainDataVector.clear();
17227  Utilities->CallLogPop(1091);
17228  return(false);
17229  }
17230  while(LastActionCommand == "Fns")
17231  {
17232  TDEntryPtr = (TDEntryPtr->ActionVector.end() - 2)->LinkedTrainEntryPtr;
17233  LastActionCommand = (TDEntryPtr->ActionVector.end() - 2)->Command;
17234  if((LastActionCommand != "Fns") && (LastActionCommand != "Fns-sh") && (LastActionCommand != "Frh-sh"))
17235  {
17236  SecondPassMessage(GiveMessages,
17237  "Error in timetable - last event in a continuation shuttle service (i.e links back to a shuttle) whose headcode is " + TDEntryPtr->HeadCode +
17238  " is not 'Fns', 'Fns-sh' or 'Frh-sh'");
17239  TrainDataVector.clear();
17240  Utilities->CallLogPop(1092);
17241  return(false);
17242  }
17243  }
17244  // exit the 'while' with LastActionCommand FSH-XX
17245  if((TDEntryPtr->ActionVector.end() - 2)->LinkedTrainEntryPtr != ShuttleStartAddress)
17246  {
17247  SecondPassMessage(GiveMessages, "Error in timetable - the event that ends service " + TDEntryPtr->HeadCode +
17248  " is a shuttle finish, but it doesn't link back to the start of the original shuttle starting service " + OriginalHeadCode +
17249  ". The linking of two or more shuttles is not permitted.");
17250  TrainDataVector.clear();
17251  Utilities->CallLogPop(1093);
17252  return(false);
17253  }
17254  Utilities->CallLogPop(1094);
17255  return(true);
17256 }
17257 
17258 // ---------------------------------------------------------------------------
17259 
17260 void TTrainController::TimetableMessage(bool GiveMessages, AnsiString Message)
17261 {
17262  if(!GiveMessages)
17263  {
17264  return;
17265  }
17266  // if(ServiceReference == "") ShowMessage(Message);
17267  if(!CheckHeadCodeValidity(12, false, ServiceReference))
17268  {
17269  ShowMessage(ServiceReference + " (not a valid service ref.): " + Message); //amended at v2.15.1 to give information on 'service' so can find it in the list
17270  }
17271  // changed from above at v2.3.0 as a meaningless value for 'Timetable invalid - unable to find a valid start time on its own line' (uses last entry text)
17272  // false means don't give messages within the function
17273  else
17274  {
17275  ShowMessage("Service " + ServiceReference + ": " + Message);
17276  }
17277 }
17278 
17279 // ---------------------------------------------------------------------------
17280 
17281 void TTrainController::SecondPassMessage(bool GiveMessages, AnsiString Message)
17282 {
17283  if(!GiveMessages)
17284  {
17285  return;
17286  }
17287  ShowMessage(Message);
17288 }
17289 
17290 // ---------------------------------------------------------------------------
17291 
17292 AnsiString TTrainController::MinsToAnsiTime(int Input) //added at v2.15.0
17293 {
17294  TrainController->LogEvent("MinsToAnsiTime");
17295  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",MinsToAnsiTime," + Input);
17296  int Mins = Input, Hrs = 0;
17297  while(Mins > 59)
17298  {
17299  Mins -= 60;
17300  Hrs++;
17301  }
17302  AnsiString AnsiMins = AnsiString(Mins);
17303  if(AnsiMins.Length() == 1)
17304  {
17305  AnsiMins = "0" + AnsiMins;
17306  }
17307  AnsiString AnsiHrs = AnsiString(Hrs);
17308  if(AnsiHrs.Length() == 1)
17309  {
17310  AnsiHrs = "0" + AnsiHrs;
17311  }
17312  Utilities->CallLogPop(2577);
17313  return(AnsiHrs + ':' + AnsiMins);
17314 }
17315 
17316 // $$$$$$$$$$$$$$$$$$$$$$$ End of Timetable Functions $$$$$$$$$$$$$$$$$$$$$$$
17317 // ---------------------------------------------------------------------------
17318 
17319 void TTrainController::LogActionError(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionEventType ActionEventType, AnsiString LocationID)
17320 // FailTrainEntry: 06:00:10 HELD: 2F43 can't enter railway, train obstructing entry position 57-N5
17321 // FailCreateTrain: 06:00:10 HELD: 2F43 can't be created, train obstructing start position 57-N5
17322 // FailCreateLockedRoute: 06:00:10 HELD: 2F43 can't be created on a locked route - start position 57-N5
17323 // FailEnterLockedRoute: 06:00:10 HELD: 2F43 can't enter on a locked route - start position 57-N5
17324 // FailCreatePoints: 06:00:10 HELD: 2F43 can't be created, points set against start position 57-N5
17325 // FailUnexpectedExitRailway: 06:00:10 ERROR: 2F43 left railway unexpectedly at position 57-N5
17326 // FailIncorrectExit: 06:00:10 ERROR: 2F43 left railway at an incorrect exit at position 57-N5
17327 // FailSPAD: 06:00:10 ERROR: 2F43 PASSED SIGNAL AT DANGER at position 57-N5
17328 // FailLockedRoute: 06:00:10 ERROR: SPAD Risk! Signals reset ahead of train, at position 57-N5
17329 // FailLocTooShort: 06:00:10 ERROR: 2F43 failed to split - location too short at Essex Road
17330 // FailSplitDueToOtherTrain: 06:00:10 HELD: 2F43 unable to split - another train is obstructing at Essex Road, please move it if possible
17331 // FailCrashed: 06:00:10: ERROR: 2F43 CRASHED INTO 3F43 at position 46-N7
17332 // FailDerailed: 06:00:10: ERROR: 2F43 DERAILED at position 46-N7
17333 // FailUnexpectedBuffers: 06:00:10: ERROR: 2F43 stopped at buffers unexpectedly at position 46-N7
17334 // FailMissedArrival: 06:00:10: ERROR: 2F43 failed to stop at Essex Road;
17335 // FailMissedSplit: 06:00:10: ERROR: 2F43 failed to split at Essex Road
17336 // FailMissedJBO: 06:00:10: ERROR: 2F43 failed to be joined by join other train at Essex Road
17337 // FailMissedDSC: 06:00:10: ERROR: 2F43 failed to change its description at Essex Road
17338 // FailMissedCMS: 06:00:10: ERROR: 2F43 failed to change its maximum speed at Essex Road
17339 // FailMissedJoinOther: 06:00:10: ERROR: 2F43 failed to join other train at Essex Road
17340 // FailMissedTerminate: 06:00:10: ERROR: 2F43 failed to terminate at Essex Road
17341 // FailMissedNewService: 06:00:10: ERROR: 2F43 failed to form new service at Essex Road
17342 // FailMissedExitRailway: 06:00:10: ERROR: 2F43 failed to exit railway
17343 // FailMissedChangeDirection: 06:00:10: ERROR: 2F43 failed to change direction at Essex Road
17344 // FailMissedPass: 06:00:10: ERROR: 2F43 failed to pass Essex Road
17345 // FailBuffersPreventingStart: 06:00:10: ERROR: 2F43 facing buffers and unable to start at Essex Road
17346 // FailBufferCrash: 06:00:10: ERROR: 2F43 CRASHED INTO BUFFERS at 46-N7
17347 // FailLevelCrossingCrash: 06:00:10: ERROR: 2F43 CRASHED INTO ROAD TRAFFIC AT A LEVEL CROSSING at 46-N7
17348 // RouteForceCancelled: 06:00:10: ERROR: 2F43 forced a route cancellation by occupying it incorrectly at 46-N7
17349 // WaitingForJBO: 06:00:10: WARNING: 2F43 waiting to join 3F43 at Essex Road
17350 // WaitingForFJO: 06:00:10: WARNING: 2F43 waiting to be joined by 3F43 at Essex Road
17351 // FailEntryRouteSetAgainst: 06:00:10: WARNING: 2F43 can't enter railway, route set against it at entry position 57-N5 //added at v2.9.1
17352 // FailNoPowerUnableToDepart: 06:00:10: WARNING: 2F43 is without power so it can't depart from Essex Road // added at v2.19.1
17353 // FailTrainInFront 06:00:10: WARNING: 2F43 can't depart because there is a train in front at Essex Road // added at v2.19.1
17354 
17355 {
17356  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LogActionError," + HeadCode + "," + OtherHeadCode + "," +
17357  AnsiString(ActionEventType) + "," + LocationID);
17358  AnsiString BaseLog = "", Prefix = "", ErrorLog = "", WarningStr = "";
17359 
17360  TDateTime ActualTime = TrainController->TTClockTime; //moved from lower down at v2.9.1
17361  AnsiString TimeAndHeadCode = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode; //added at v2.9.1 to give more info to user
17362 
17363  Prefix = " ERROR: ";
17364  if(ActionEventType == FailTrainEntry)
17365  {
17366  Prefix = " HELD: ";
17367  ErrorLog = " can't enter railway, train obstructing entry position ";
17368  WarningStr = " can't enter railway, train obstructing entry position ";
17369  Display->WarningLog(1, TimeAndHeadCode + WarningStr + LocationID);
17370  }
17371  else if(ActionEventType == FailEntryRouteSetAgainst) //added at v2.9.1
17372  {
17373  Prefix = " HELD: ";
17374  ErrorLog = " can't enter railway, route set against it at entry position ";
17375  WarningStr = " can't enter railway, route set against it at entry position ";
17376  Display->WarningLog(10, TimeAndHeadCode + WarningStr + LocationID);
17377  }
17378  else if(ActionEventType == FailCreateTrain)
17379  {
17380  Prefix = " HELD: ";
17381  ErrorLog = " can't be created, train obstructing at ";
17382  WarningStr = " can't be created, train obstructing at ";
17383  Display->WarningLog(2, TimeAndHeadCode + WarningStr + LocationID);
17384  }
17385  else if(ActionEventType == FailCreateLockedRoute)
17386  {
17387  Prefix = " HELD: ";
17388  ErrorLog = " can't be created on a locked route at ";
17389  WarningStr = " can't be created on a locked route at ";
17390  Display->WarningLog(4, TimeAndHeadCode + WarningStr + LocationID);
17391  }
17392  else if(ActionEventType == FailEnterLockedRoute)
17393  {
17394  Prefix = " HELD: ";
17395  ErrorLog = " can't enter on a locked route at ";
17396  WarningStr = " can't enter on a locked route at ";
17397  Display->WarningLog(5, TimeAndHeadCode + WarningStr + LocationID);
17398  }
17399  else if(ActionEventType == FailCreatePoints)
17400  {
17401  Prefix = " HELD: ";
17402  ErrorLog = " can't be created, points set wrongly at ";
17403  WarningStr = " can't be created, points set wrongly at ";
17404  Display->WarningLog(3, TimeAndHeadCode + WarningStr + LocationID);
17405  }
17406  else if(ActionEventType == FailUnexpectedExitRailway)
17407  {
17408  ErrorLog = " left railway unexpectedly at ";
17409  UnexpectedExits++;
17410  }
17411  else if(ActionEventType == FailIncorrectExit)
17412  {
17413  ErrorLog = " left railway at an incorrect exit at ";
17414  IncorrectExits++;
17415  }
17416  else if(ActionEventType == FailLocTooShort)
17417  {
17418  ErrorLog = " failed to split - location too short at ";
17419  WarningStr = " failed to split, location too short at ";
17420  Display->WarningLog(6, TimeAndHeadCode + WarningStr + LocationID);
17421  }
17422  else if(ActionEventType == FailSplitDueToOtherTrain)
17423  {
17424  Prefix = " HELD: ";
17425  ErrorLog = " unable to split - other train obstructing at ";
17426  WarningStr = " unable to split - other train obstructing at ";
17427  Display->WarningLog(7, TimeAndHeadCode + WarningStr + LocationID);
17428  }
17429  else if(ActionEventType == FailUnexpectedBuffers)
17430  {
17431  ErrorLog = " stopped at buffers unexpectedly at position ";
17432  }
17433  else if(ActionEventType == FailMissedArrival)
17434  {
17435  ErrorLog = " failed to stop at ";
17436  MissedStops++;
17437  }
17438  else if(ActionEventType == FailMissedSplit)
17439  {
17440  ErrorLog = " failed to split at ";
17442  }
17443  else if(ActionEventType == FailMissedJBO)
17444  {
17445  ErrorLog = " failed to be joined by other train at ";
17447  }
17448  else if(ActionEventType == FailMissedDSC) //new at v2.15.0
17449  {
17450  ErrorLog = " failed to change its description at ";
17451 // OtherMissedEvents++; shouldn't count
17452  }
17453  else if(ActionEventType == FailMissedCMS) //new at v2.21.0
17454  {
17455  ErrorLog = " failed to change its maximum speed at ";
17456 // OtherMissedEvents++; shouldn't count
17457  }
17458  else if(ActionEventType == FailMissedJoinOther)
17459  {
17460  ErrorLog = " failed to join other train at ";
17462  }
17463  else if(ActionEventType == FailMissedTerminate)
17464  {
17465  ErrorLog = " failed to terminate at ";
17467  }
17468  else if(ActionEventType == FailMissedNewService)
17469  {
17470  ErrorLog = " failed to form new service at ";
17472  }
17473  else if(ActionEventType == FailMissedExitRailway)
17474  {
17475  ErrorLog = " failed to exit railway ";
17477  }
17478  else if(ActionEventType == FailMissedChangeDirection)
17479  {
17480  ErrorLog = " failed to change direction at ";
17481 // OtherMissedEvents++; //dropped at v2.12.0 as cdt shouldn't count
17482  }
17483  else if(ActionEventType == FailMissedPass)
17484  {
17485  ErrorLog = " failed to pass ";
17486 // OtherMissedEvents++; //dropped at v2.12.0 as missed pass shouldn't count
17487  }
17488  else if(ActionEventType == FailBuffersPreventingStart)
17489  {
17490  ErrorLog = " facing buffers and unable to start at ";
17491  }
17492  else if(ActionEventType == FailDerailed)
17493  {
17494  ErrorLog = " DERAILED at position ";
17495  Prefix = " DERAILMENT: ";
17496  Derailments++;
17497  }
17498  else if(ActionEventType == FailBufferCrash)
17499  {
17500  ErrorLog = " CRASHED INTO BUFFERS at ";
17501  Prefix = " CRASH: ";
17502  CrashedTrains++;
17503  }
17504  else if(ActionEventType == FailLevelCrossingCrash)
17505  {
17506  ErrorLog = " CRASHED INTO ROAD TRAFFIC AT A LEVEL CROSSING at ";
17507  Prefix = " CRASH: ";
17508  CrashedTrains++;
17509  }
17510  else if(ActionEventType == FailCrashed)
17511  {
17512  ErrorLog = " CRASHED INTO " + OtherHeadCode + " at position ";
17513  Prefix = " CRASH: ";
17514  CrashedTrains++;
17515  CrashedTrains++;
17516  }
17517  else if(ActionEventType == FailSPAD)
17518  {
17519  ErrorLog = " PASSED SIGNAL AT DANGER at position ";
17520  Prefix = " SPAD: ";
17521  SPADEvents++;
17522  }
17523  else if(ActionEventType == FailLockedRoute)
17524  {
17525  ErrorLog = "Signals reset ahead of train, route cancelled at position ";
17526  Prefix = " SPAD RISK: ";
17527  SPADRisks++;
17528  }
17529  else if(ActionEventType == RouteForceCancelled)
17530  {
17531  ErrorLog = " forced a route cancellation by occupying it incorrectly at ";
17532  }
17533  else if(ActionEventType == WaitingForJBO)
17534  {
17535  Prefix = " WARNING: ";
17536  ErrorLog = " waiting to join " + OtherHeadCode + " at ";
17537  WarningStr = " waiting to join " + OtherHeadCode + " at ";
17538  Display->WarningLog(8, TimeAndHeadCode + WarningStr + LocationID);
17539  }
17540  else if(ActionEventType == WaitingForFJO)
17541  {
17542  Prefix = " WARNING: ";
17543  ErrorLog = " waiting to be joined by " + OtherHeadCode + " at ";
17544  WarningStr = " waiting to be joined by " + OtherHeadCode + " at ";
17545  Display->WarningLog(9, TimeAndHeadCode + WarningStr + LocationID);
17546  }
17547  else if(ActionEventType == FailNoPowerUnableToDepart) //06:00:10: WARNING: 2F43 is without power so it can't depart from Essex Road // added at v2.19.1
17548  {
17549  Prefix = " WARNING: ";
17550  ErrorLog = " is without power so it can't depart from ";
17551  WarningStr = " is without power so it can't depart from ";
17552  Display->WarningLog(27, TimeAndHeadCode + WarningStr + LocationID);
17553  }
17554  else if(ActionEventType == FailTrainInFront) //06:00:10: WARNING: 2F43 can't depart because there is a train in front at Essex Road // added at v2.19.1
17555  {
17556  Prefix = " WARNING: ";
17557  ErrorLog = " can't depart because there is a train in front at ";
17558  WarningStr = " can't depart because there is a train in front at ";
17559  Display->WarningLog(28, TimeAndHeadCode + WarningStr + LocationID);
17560  }
17561 
17562  BaseLog = Utilities->Format96HHMMSS(ActualTime) + Prefix + HeadCode;
17563  PerfLogForm->PerformanceLog(4, BaseLog + ErrorLog + LocationID);
17564  Utilities->CallLogPop(1371);
17565 }
17566 
17567 // ---------------------------------------------------------------------------
17568 
17570 {
17571 /* //for testing purposes
17572  TrainDataEntry
17573  AnsiString HeadCode, Description;//null on creation
17574  int StartSpeed, MaxRunningSpeed;//both kph
17575  int RepeatNumber;
17576  TActionVector ActionVector;
17577  TTrainOperatingDataVector TrainOperatingDataVector;//no of repeats + 1
17578  TTrainDataEntry() {StartSpeed=0; MaxRunningSpeed=0; RepeatNumber=0;}
17579 
17580  ActionVectorEntry
17581  TTimetableEntryType FormatType;
17582  TDateTime EventTime, ArrivalTime, DepartureTime;//zeroed on creation so change to -1 as a marker for 'not set'
17583  AnsiString LocationName, Command, OtherHeadCode;//null on creation
17584  TActionVectorEntry *OtherHeadCodeStartingEntryPtr;
17585  int RearStartOrRepeatMins, FrontStartOrRepeatDigits;
17586  int RepeatNumber;
17587 
17588  TrainOperatingData
17589  int Mass, MaxBrakeRate, PowerAtRail;//kg;m/s/s;W
17590  int TrainID;
17591  TRunningEntry RunningEntry;
17592  TDateTime StartTime;
17593  AnsiString HeadCode;
17594 */
17595  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveTrainDataVectorToFile");
17596  std::ofstream OutFile("TrainData.csv");
17597 
17598  if(OutFile == 0)
17599  {
17600  ShowMessage("Output file TrainData.csv failed to open");
17601  Utilities->CallLogPop(1372);
17602  return;
17603  }
17604  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
17605  {
17606  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
17607  OutFile << "HeadCode" << ',' << "Description" << ',' << "StartSpeed" << ',' << "MaxRunningSpeed" << ',' << "NumberOfTrains" << '\n' << '\n';
17608 
17609  OutFile << TDEntry.HeadCode.c_str() << ',' << TDEntry.FixedDescription.c_str() /* name changed at v2.16.1*/
17610  << ',' << TDEntry.StartSpeed << ',' << TDEntry.MaxRunningSpeed << ',' << TDEntry.NumberOfTrains << '\n' << '\n';
17611 
17612  OutFile << ',' << "FormatType" << ',' << "EventTime" << ',' << "ArrivalTime" << ',' << "DepartureTime" << ',' << "LocationName" << ',' << "Command" <<
17613  ',' << "OtherHeadCode" << ',' << "LinkedTrainEntryPtr" << ',' << "RearStartOrRepeatMins" << ',' << "FrontStartOrRepeatDigits" << ',' <<
17614  "RepeatNumber" << '\n' << '\n';
17615  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
17616  {
17617  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
17618  AnsiString TimetableEntryTypeStr;
17619  // NoFormat, TimeLoc, TimeTimeLoc, TimeCmd, StartNew, TimeCmdHeadCode, FinRemHere, FNSNonRepeatToShuttle, SNTShuttle, SNSShuttle, SNSNonRepeatFromShuttle, FSHNewService, Repeat
17620  switch(AVEntry.FormatType)
17621  {
17622  case 0:
17623  {
17624  TimetableEntryTypeStr = "NoFormat";
17625  break;
17626  }
17627 
17628  case 1:
17629  {
17630  TimetableEntryTypeStr = "TimeLoc";
17631  break;
17632  }
17633 
17634  case 2:
17635  {
17636  TimetableEntryTypeStr = "TimeTimeLoc";
17637  break;
17638  }
17639 
17640  case 3:
17641  {
17642  TimetableEntryTypeStr = "TimeCmd";
17643  break;
17644  }
17645 
17646  case 4:
17647  {
17648  TimetableEntryTypeStr = "StartNew";
17649  break;
17650  }
17651 
17652  case 5:
17653  {
17654  TimetableEntryTypeStr = "TimeCmdHeadCode";
17655  break;
17656  }
17657 
17658  case 6:
17659  {
17660  TimetableEntryTypeStr = "FinRemHere";
17661  break;
17662  }
17663 
17664  case 7:
17665  {
17666  TimetableEntryTypeStr = "FNSShuttle";
17667  break;
17668  }
17669 
17670  case 8:
17671  {
17672  TimetableEntryTypeStr = "SNTShuttle";
17673  break;
17674  }
17675 
17676  case 9:
17677  {
17678  TimetableEntryTypeStr = "SNSShuttle";
17679  break;
17680  }
17681 
17682  case 10:
17683  {
17684  TimetableEntryTypeStr = "SNSNonRepeatFromShuttle";
17685  break;
17686  }
17687 
17688  case 11:
17689  {
17690  TimetableEntryTypeStr = "FSHNewService";
17691  break;
17692  }
17693 
17694  case 12:
17695  {
17696  TimetableEntryTypeStr = "Repeat";
17697  break;
17698  }
17699 
17700  default:
17701  {
17702  TimetableEntryTypeStr = "Default";
17703  break;
17704  }
17705  }
17706  OutFile << ',' << TimetableEntryTypeStr.c_str() << ',' << Utilities->Format96HHMM(AVEntry.EventTime).c_str() << ',' << Utilities->Format96HHMM
17707  (AVEntry.ArrivalTime).c_str() << ',' << Utilities->Format96HHMM(AVEntry.DepartureTime).c_str() << ',' << AVEntry.LocationName.c_str()
17708  << ',' << AVEntry.Command.c_str() << ',' << AVEntry.OtherHeadCode.c_str()
17709  << ',' << AVEntry.LinkedTrainEntryPtr << ',' << AVEntry.RearStartOrRepeatMins << ',' << AVEntry.FrontStartOrRepeatDigits << ',' <<
17710  AVEntry.NumberOfRepeats << '\n';
17711  }
17712  OutFile << '\n';
17713  OutFile << ',' << ',' << "Mass" << ',' << "MaxBrakeRate" << ',' << "PowerAtRail" << ',' << "TrainID" << ',' << "RunningEntry" << '\n' << '\n';
17714  for(unsigned int y = 0; y < TrainDataVector.at(x).TrainOperatingDataVector.size(); y++)
17715  {
17716  TTrainOperatingData TOD = TrainDataVector.at(x).TrainOperatingDataVector.at(y);
17717  AnsiString RunningEntryStr;
17718  // NotStarted, Running, Exited
17719  switch(TOD.RunningEntry)
17720  {
17721  case 0:
17722  {
17723  RunningEntryStr = "NotStarted";
17724  break;
17725  }
17726 
17727  case 1:
17728  {
17729  RunningEntryStr = "Running";
17730  break;
17731  }
17732 
17733  case 2:
17734  {
17735  RunningEntryStr = "Exited";
17736  break;
17737  }
17738  }
17739  OutFile << ',' << ',' << TOD.TrainID << ',' << RunningEntryStr.c_str() << ',' << '\n';
17740  }
17741  OutFile << '\n';
17742  }
17743  OutFile.close();
17744  Utilities->CallLogPop(1373);
17745 }
17746 
17747 // ---------------------------------------------------------------------------
17748 
17749 void TTrainController::StopTTClockMessage(int Caller, AnsiString Message)
17750 // ShowMessage stops everything so this function used where a message is needed when may be in Operating mode.
17751 // The timetable Restart and BaseTimes are reset so the timetable clock stops & restarts when 'OK' button pressed (in ClockTimer2 when StopTTClockFlag is false)
17752 {
17753  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StopTTClockMessage," + Message);
17754  StopTTClockFlag = true; // so TTClock stopped during MasterClockTimer function
17756  ShowMessage(Message);
17757  BaseTime = TDateTime::CurrentDateTime();
17758  StopTTClockFlag = false;
17759  Utilities->CallLogPop(1374);
17760 }
17761 
17762 // ---------------------------------------------------------------------------
17763 
17764 void TTrainController::SaveSessionTrains(int Caller, std::ofstream &SessionFile)
17765 // save *TrainDataEntryPtr & *ActionVectorEntryPtr as integer offsets
17766 // from the start of the relevant vectors. Can't save the pointer values
17767 // as these will be different each time the vectors are created
17768 {
17769  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionTrains");
17770  Utilities->SaveFileInt(SessionFile, TrainVector.size());
17771  for(unsigned int x = 0; x < TrainVector.size(); x++)
17772  {
17773  TrainVectorAt(55, x).SaveOneSessionTrain(0, SessionFile);
17774  }
17775  Utilities->CallLogPop(1375);
17776 }
17777 
17778 // ---------------------------------------------------------------------------
17779 
17780 void TTrainController::LoadSessionTrains(int Caller, std::ifstream &SessionFile)
17781 {
17782  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionTrains");
17783  int NumberOfTrains = Utilities->LoadFileInt(SessionFile);
17784  TTrain *NewTrain = new TTrain(1, 0, 0, "", 0, 1, 0, 0, 0, (TTrainMode)0, 0, 0, 0, 0, 0); // have to have >0 for mass, else have divide
17785  // by zero error in calculating AValue, use 1
17786  for(int x = 0; x < NumberOfTrains; x++)
17787  {
17788  *NewTrain = TTrain(2, 0, 0, "", 0, 1, 0, 0, 0, (TTrainMode)0, 0, 0, 0, 0, 0); // have to have >0 for mass, else have divide
17789  // by zero error in calculating AValue, use 1
17790  NewTrain->LoadOneSessionTrain(0, SessionFile);
17791  if((NewTrain->EntrySpeed < 1) && (NewTrain->PowerAtRail < 1))
17792  // added at v2.4.0. have to include as that value not stored in session file
17793  {
17794  NewTrain->StoppedWithoutPower = true;
17795  }
17796  TrainVector.push_back(*NewTrain);
17797  LastTrainLoaded = x;
17798  }
17799  delete NewTrain;
17800  Utilities->CallLogPop(1376);
17801 }
17802 
17803 // ---------------------------------------------------------------------------
17804 
17805 bool TTrainController::CheckSessionTrains(int Caller, std::ifstream &InFile)
17806 {
17807  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionTrains");
17808  int NumberOfTrains;
17809 
17810  if(!Utilities->CheckAndReadFileInt(InFile, 0, 10000, NumberOfTrains))
17811  {
17812  Utilities->CallLogPop(1377);
17813  return(false);
17814  }
17815  for(int x = 0; x < NumberOfTrains; x++)
17816  {
17817  if(!(TTrain::CheckOneSessionTrain(InFile)))
17818  {
17819  Utilities->CallLogPop(1378);
17820  return(false);
17821  }
17822  }
17823  Utilities->CallLogPop(1379);
17824  return(true);
17825 }
17826 
17827 // ---------------------------------------------------------------------------
17828 
17829 void TTrainController::SaveSessionLockedRoutes(int Caller, std::ofstream &SessionFile)
17830 {
17831  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionLockedRoutes");
17832  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.size());
17833  for(unsigned int x = 0; x < AllRoutes->LockedRouteVector.size(); x++)
17834  {
17835  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).RouteNumber);
17836  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).RearTrackVectorPosition);
17837  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).LastTrackVectorPosition);
17838  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).LastXLinkPos);
17839  Utilities->SaveFileDouble(SessionFile, double(AllRoutes->LockedRouteVector.at(x).LockStartTime));
17840  }
17841  Utilities->CallLogPop(1380);
17842 }
17843 
17844 // ---------------------------------------------------------------------------
17845 
17846 void TTrainController::LoadSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
17847 {
17848  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionLockedRoutes");
17849  TAllRoutes::TLockedRouteClass LockedRouteObject;
17850  int LockedRouteVectorSize = Utilities->LoadFileInt(SessionFile);
17851 
17852  for(int x = 0; x < LockedRouteVectorSize; x++)
17853  {
17854  LockedRouteObject.RouteNumber = Utilities->LoadFileInt(SessionFile);
17855  LockedRouteObject.RearTrackVectorPosition = Utilities->LoadFileInt(SessionFile);
17856  LockedRouteObject.LastTrackVectorPosition = Utilities->LoadFileInt(SessionFile);
17857  LockedRouteObject.LastXLinkPos = Utilities->LoadFileInt(SessionFile);
17858  double LockStartTimeDouble = Utilities->LoadFileDouble(SessionFile);
17859  LockedRouteObject.LockStartTime = TDateTime(LockStartTimeDouble);
17860  AllRoutes->LockedRouteVector.push_back(LockedRouteObject);
17861  }
17862  Utilities->CallLogPop(1381);
17863 }
17864 
17865 // ---------------------------------------------------------------------------
17866 
17867 bool TTrainController::CheckSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
17868 {
17869  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionLockedRoutes");
17870  int LockedRouteVectorSize;
17871 
17872  if(!Utilities->CheckAndReadFileInt(SessionFile, 0, 10000, LockedRouteVectorSize))
17873  {
17874  Utilities->CallLogPop(1382);
17875  return(false);
17876  }
17877  for(int x = 0; x < LockedRouteVectorSize; x++)
17878  {
17879  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17880  {
17881  Utilities->CallLogPop(1383);
17882  return(false);
17883  }
17884  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17885  {
17886  Utilities->CallLogPop(1384);
17887  return(false);
17888  }
17889  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17890  {
17891  Utilities->CallLogPop(1385);
17892  return(false);
17893  }
17894  if(!Utilities->CheckFileInt(SessionFile, 0, 3))
17895  {
17896  Utilities->CallLogPop(1386);
17897  return(false);
17898  }
17899  if(!Utilities->CheckFileDouble(SessionFile))
17900  {
17901  Utilities->CallLogPop(1387);
17902  return(false);
17903  }
17904  }
17905  Utilities->CallLogPop(1388);
17906  return(true);
17907 }
17908 
17909 // ---------------------------------------------------------------------------
17910 
17911 void TTrainController::SaveSessionContinuationAutoSigEntries(int Caller, std::ofstream &SessionFile)
17912 {
17913  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionContinuationAutoSigEntries");
17914  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.size());
17915  for(unsigned int x = 0; x < ContinuationAutoSigVector.size(); x++)
17916  {
17917  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.at(x).RouteNumber);
17918  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.at(x).AccessNumber);
17919  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).FirstDelay);
17920  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).SecondDelay);
17921  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).ThirdDelay);
17922  Utilities->SaveFileDouble(SessionFile, double(ContinuationAutoSigVector.at(x).PassoutTime));
17923  }
17924  Utilities->CallLogPop(1389);
17925 }
17926 
17927 // ---------------------------------------------------------------------------
17928 
17929 void TTrainController::LoadSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
17930 {
17931  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionContinuationAutoSigEntries");
17932  TContinuationAutoSigEntry ContinuationAutoSigObject;
17933  int ContinuationAutoSigVectorSize = Utilities->LoadFileInt(SessionFile);
17934 
17935  for(int x = 0; x < ContinuationAutoSigVectorSize; x++)
17936  {
17937  ContinuationAutoSigObject.RouteNumber = Utilities->LoadFileInt(SessionFile);
17938  ContinuationAutoSigObject.AccessNumber = Utilities->LoadFileInt(SessionFile);
17939  ContinuationAutoSigObject.FirstDelay = Utilities->LoadFileDouble(SessionFile);
17940  ContinuationAutoSigObject.SecondDelay = Utilities->LoadFileDouble(SessionFile);
17941  ContinuationAutoSigObject.ThirdDelay = Utilities->LoadFileDouble(SessionFile);
17942  double PassoutTimeDouble = Utilities->LoadFileDouble(SessionFile);
17943  ContinuationAutoSigObject.PassoutTime = TDateTime(PassoutTimeDouble);
17944  ContinuationAutoSigVector.push_back(ContinuationAutoSigObject);
17945  }
17946  Utilities->CallLogPop(1390);
17947 }
17948 
17949 // ---------------------------------------------------------------------------
17950 
17951 bool TTrainController::CheckSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
17952 {
17953  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionContinuationAutoSigEntries");
17954  int ContinuationAutoSigVectorSize;
17955 
17956  if(!Utilities->CheckAndReadFileInt(SessionFile, 0, 10000, ContinuationAutoSigVectorSize))
17957  {
17958  Utilities->CallLogPop(1391);
17959  return(false);
17960  }
17961  for(int x = 0; x < ContinuationAutoSigVectorSize; x++)
17962  {
17963  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17964  {
17965  Utilities->CallLogPop(1392);
17966  return(false);
17967  }
17968  if(!Utilities->CheckFileInt(SessionFile, 0, 3))
17969  {
17970  Utilities->CallLogPop(1393);
17971  return(false);
17972  }
17973  if(!Utilities->CheckFileDouble(SessionFile))
17974  {
17975  Utilities->CallLogPop(1405);
17976  return(false);
17977  }
17978  if(!Utilities->CheckFileDouble(SessionFile))
17979  {
17980  Utilities->CallLogPop(1406);
17981  return(false);
17982  }
17983  if(!Utilities->CheckFileDouble(SessionFile))
17984  {
17985  Utilities->CallLogPop(1407);
17986  return(false);
17987  }
17988  if(!Utilities->CheckFileDouble(SessionFile))
17989  {
17990  Utilities->CallLogPop(1394);
17991  return(false);
17992  }
17993  }
17994  Utilities->CallLogPop(1395);
17995  return(true);
17996 }
17997 
17998 // ---------------------------------------------------------------------------
17999 
18000 /*
18001  class TContinuationTrainExpectationEntry //for expected trains at continuation entries
18002  {
18003  public:
18004  AnsiString Description; ///< service description
18005  AnsiString HeadCode; ///< service headcode
18006  int RepeatNumber; ///< service RepeatNumber
18007  int IncrementalMinutes; ///< Repeat separation in minutes
18008  int IncrementalDigits; ///< Repeat headcode separation
18009  int VectorPosition; ///< TrackVectorPosition for the continuation element
18010  TTrainDataEntry *TrainDataEntryPtr; ///< points to the service entry in the timetable's TrainDataVector
18011  };
18012 
18013 
18014  typedef std::multimap<TDateTime,TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMap;
18015  typedef pair<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMapPair;
18016 */
18017 
18019 // build this into timetable load so session loading can use it too
18020 // being a multimap it automatically sorts in ascending EventTime order
18021 {
18022  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",BuildContinuationTrainExpectationMultiMap");
18024  // need to clear as this called twice when load a session
18025  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
18026  {
18027  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
18028  const TActionVectorEntry &AVFirstEntry = TDEntry.ActionVector.at(0);
18029  const TActionVectorEntry &AVLastEntry = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
18030 
18031  if(AVFirstEntry.Command == "Snt")
18032  // new train (no need to include Snt-sh since they can't start at a continuation)
18033  {
18036  {
18038  CTEEntry.VectorPosition = AVFirstEntry.RearStartOrRepeatMins;
18039  // retains this value for all repeats
18040  CTEEntry.RepeatNumber = 0; // for first entry
18041  CTEEntry.TrainDataEntryPtr = &TDEntry;
18042  // retains this value for all repeats
18043  CTEEntry.HeadCode = TDEntry.HeadCode;
18044  CTEEntry.FixedDescription = TDEntry.FixedDescription; //name changed at v2.16.1
18045  CTEEntry.IncrementalMinutes = 0;
18046  CTEEntry.IncrementalDigits = 0;
18047  if(AVLastEntry.FormatType == Repeat)
18048  {
18049  CTEEntry.IncrementalMinutes = AVLastEntry.RearStartOrRepeatMins;
18050  // retains this value or 0 for all repeats
18051  CTEEntry.IncrementalDigits = AVLastEntry.FrontStartOrRepeatDigits;
18052  // retains this value or 0 for all repeats
18053  }
18054  CTEMMP.first = AVFirstEntry.EventTime;
18055  CTEMMP.second = CTEEntry;
18056  ContinuationTrainExpectationMultiMap.insert(CTEMMP);
18057  // base entry
18058  if(TDEntry.NumberOfTrains > 1)
18059  {
18060  if(AVLastEntry.FormatType != Repeat)
18061  {
18062  throw Exception("Error, Last ActionVectorEntry not a repeat in BuildContinuationTrainExpectationMultiMap");
18063  }
18064  for(int y = 1; y < TDEntry.NumberOfTrains; y++)
18065  {
18066  CTEEntry.RepeatNumber = y;
18067  CTEEntry.HeadCode = GetRepeatHeadCode(23, TDEntry.HeadCode, y, AVLastEntry.FrontStartOrRepeatDigits);
18068  // CTEEntry.VectorPosition stays same
18069  CTEMMP.first = GetRepeatTime(3, AVFirstEntry.EventTime, y, AVLastEntry.RearStartOrRepeatMins);
18070  CTEMMP.second = CTEEntry;
18071  ContinuationTrainExpectationMultiMap.insert(CTEMMP);
18072  }
18073  }
18074  }
18075  }
18076  }
18077  Utilities->CallLogPop(1396);
18078 }
18079 
18080 // ---------------------------------------------------------------------------
18081 
18083 {
18084  // called when WarningFlashCount == 0 or when press zoomout button
18085  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainsInZoomOutMode");
18086  if(!Display->ZoomOutFlag)
18087  {
18088  Utilities->CallLogPop(1156);
18089  return;
18090  }
18091  for(unsigned int x = 0; x < TrainVector.size(); x++)
18092  {
18093  // plot blanks & track for all train, even if to be overplotted, since when flashing need to overplot all anyway
18094  // if OldPlotElement[x] == -1 then ignore (not plotted)
18096  TrainVectorAt(57, x).PlotTrainInZoomOutMode(0, Flash);
18097  }
18098  Display->Update();
18099  // need to keep this since Update() not called for PlotSmallOutput as too slow
18100  Utilities->CallLogPop(742);
18101 }
18102 
18103 // ---------------------------------------------------------------------------
18104 
18105 TTrain &TTrainController::TrainVectorAt(int Caller, int VecPos)
18106 {
18107  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainVectorAt," + AnsiString(VecPos));
18108  if((VecPos < 0) || (VecPos >= (int)TrainVector.size()))
18109  {
18110  throw Exception("Out of Range Error, vector size: " + AnsiString(TrainVector.size()) + ", VecPos: " + AnsiString(VecPos) + " in TrainVectorAt");
18111  }
18112  Utilities->CallLogPop(740);
18113  return(TrainVector.at(VecPos));
18114 }
18115 
18116 // ---------------------------------------------------------------------------
18117 
18118 void TTrainController::CreateFormattedTimetable(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir)
18119 {
18120  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CreateFormattedTimetable");
18121  AnsiString RetStr = "", PartStr = "";
18122 
18123 
18124 /*
18125  Have description & mass etc for train at top - header, then array of actions
18126 
18127  class TActionVectorEntry
18128  {
18129  public:
18130  AnsiString LocationName, Command, OtherHeadCode, NonRepeatingShuttleLinkHeadCode;
18132  bool SignallerControl;
18134  bool Warning;
18136  int NumberOfRepeats;
18138  int RearStartOrRepeatMins, FrontStartOrRepeatDigits;
18140  TDateTime EventTime, ArrivalTime, DepartureTime;
18142  TNumList ExitList;
18144  TTimetableFormatType FormatType;
18146  TTimetableLocationType LocationType;
18148  TTimetableSequenceType SequenceType;
18150  TTimetableShuttleLinkType ShuttleLinkType;
18152  TTrainDataEntry *LinkedTrainEntryPtr;
18154  TTrainDataEntry *NonRepeatingShuttleLinkEntryPtr;
18156 
18157  typedef std::vector<TActionVectorEntry> TActionVector;//contains all actions for a single train
18158 
18159  enum TRunningEntry {NotStarted, Running, Exited};//contains status info for each train
18160 
18161  class TTrainOperatingData
18162  {
18163  public:
18164  int TrainID;
18165  TActionEventType EventReported;
18166  TRunningEntry RunningEntry;
18167 
18168  //inline function
18169  TTrainOperatingData() {TrainID = -1; EventReported= NoEvent; RunningEntry=NotStarted;}//ID -1 = marker for not running
18170  };
18171 
18172  typedef std::vector<TTrainOperatingData> TTrainOperatingDataVector;
18173 
18174  class TTrainDataEntry
18175  {
18176  public:
18177  AnsiString HeadCode, ServiceReference, Description;
18179  double MaxBrakeRate;
18181  double MaxRunningSpeed;
18183  double PowerAtRail;
18185  int Mass;
18187  int NumberOfTrains;
18189  int SignallerSpeed;
18191  int StartSpeed;
18193  TActionVector ActionVector;
18195  TTrainOperatingDataVector TrainOperatingDataVector;
18197 
18198  //inline function
18199  TTrainDataEntry() {StartSpeed=0; MaxRunningSpeed=0; NumberOfTrains=0;}
18200  };
18201 
18202  typedef std::vector<TTrainDataEntry> TTrainDataVector;//object is a member of TTrainController & contains the whole timetable
18203 
18204  //formatted timetable types
18205  class TOneTrainFormattedEntry
18206  {
18207  AnsiString Action;//includes location if relevanr
18208  AnsiString Time;
18209  };
18210 
18211  typedef std::vector<TOneTrainFormattedEntry> TOneFormattedTrainVector;
18212 
18213  class TOneCompleteFormattedTrain//headcode + list of actions
18214  {
18215  public:
18216  AnsiString HeadCode;
18217  TOneFormattedTrainVector OneFormattedTrainVector;
18218  };
18219 
18220  typedef std::vector<TOneCompleteFormattedTrain> TOneCompleteFormattedTrainVector;//list af all repeats
18221 
18222  class TTrainFormattedInformation//contains all information for a single TT entry (including repeats)
18223  {
18224  public:
18225  AnsiString Header;//description, mass, power, brake rate etc
18226  int NumberOfTrains;// number of repeats + 1
18227  TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector;//list af all repeats
18228  };
18229 
18230 
18231  typedef std::vector<TTrainFormattedInformation> TAllFormattedTrains;//all timetable in formatted form
18232  //end of formatted timetable types
18233 
18234 */
18235 
18236  AnsiString TTFileName = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
18237 
18238  // format "16/06/2009 20:55:17"
18239  // avoid characters in filename:= / \ : * ? " < > |
18240  TTFileName = CurDir + "\\Formatted timetables\\Timetable " + TTFileName + "; " + RailwayTitle + "; " + TimetableTitle + ".csv";
18241 
18242  AnsiString ShortTTName = "";
18243 
18244  for(int x = TTFileName.Length(); x > 0; x--)
18245  {
18246  if(TTFileName[x] == '\\')
18247  {
18248  ShortTTName = TTFileName.SubString(x + 1, TTFileName.Length() - x - 4);
18249  break;
18250  }
18251  }
18252 
18253  ShowMessage("Creates two timetables named " + ShortTTName +
18254  " in the 'Formatted timetables' folder, one in service order in '.csv' format, and one in chronological order in '.txt' format");
18255 
18256  Screen->Cursor = TCursor(-11); // Hourglass
18257 
18258  AnsiString FormatNoDPStr = "#######0";
18259  AnsiString TableTitle = "", TimetableTimeStr = "", MassStr = "", PowerStr = "", BrakeStr = "", MaxSpeedStr = "", FirstHeadCode = "", Header = "";
18260 
18262  TableTitle = "Railway: " + RailwayTitle + "; Timetable: " + TimetableTitle + "; Start time: " + TimetableTimeStr;
18263  TAllFormattedTrains *AllTTTrains = new TAllFormattedTrains;
18264 
18265  // all timetable in formatted form
18266  //create the AllTTTrains vector
18267  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
18268  {
18269  MassStr = "", PowerStr = "", BrakeStr = "", MaxSpeedStr = "";
18270  const TTrainDataEntry &TrainDataEntry = TrainDataVector.at(x);
18271  if(TrainDataEntry.Mass > 0)
18272  {
18273  MassStr = "; Mass " + AnsiString::FormatFloat(FormatNoDPStr, ((double)TrainDataEntry.Mass) / 1000) + "Te; ";
18274  }
18275  if(TrainDataEntry.PowerAtRail > 0)
18276  {
18277  PowerStr = "Power " + AnsiString::FormatFloat(FormatNoDPStr, TrainDataEntry.PowerAtRail / 1000 / 0.8) + "kW; ";
18278  }
18279  if(TrainDataEntry.MaxBrakeRate > 0)
18280  {
18281  BrakeStr = "Brake force " + AnsiString::FormatFloat(FormatNoDPStr, (TrainDataEntry.MaxBrakeRate * TrainDataEntry.Mass / 9810)) + "Te; ";
18282  }
18283  if(TrainDataEntry.MaxRunningSpeed > 0)
18284  {
18285  MaxSpeedStr = "Maximum speed " + AnsiString::FormatFloat(FormatNoDPStr, TrainDataEntry.MaxRunningSpeed) + " km/h";
18286  }
18287  FirstHeadCode = TrainDataEntry.HeadCode;
18288  int IncDigits = 0, IncMinutes = 0;
18289  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
18290  if(!ActionVector.empty())
18291  {
18292  if(ActionVector.at(ActionVector.size() - 1).FormatType == Repeat)
18293  {
18294  IncDigits = ActionVector.at(ActionVector.size() - 1).FrontStartOrRepeatDigits;
18295  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
18296  }
18297  }
18298  TTrainFormattedInformation OneTTLine;
18299  // contains all information for a single TT entry (including repeats)
18300  for(int y = 0; y < TrainDataEntry.NumberOfTrains; y++)
18301  {
18302  OneTTLine.Header = "";
18303  if((TrainDataEntry.FixedDescription != "") && (MassStr != "")) //name changed at v2.16.1
18304  {
18305  OneTTLine.Header = TrainDataEntry.FixedDescription + MassStr + PowerStr + BrakeStr + MaxSpeedStr; //name changed at v2.16.1
18306  }
18307  else if(TrainDataEntry.FixedDescription != "") //name changed at v2.16.1
18308  {
18309  OneTTLine.Header = TrainDataEntry.FixedDescription; //name changed at v2.16.1
18310  }
18311  OneTTLine.NumberOfTrains = TrainDataEntry.NumberOfTrains;
18312  TOneCompleteFormattedTrain OneTTTrain; // headcode + list of actions
18313  for(unsigned int z = 0; z < ActionVector.size(); z++)
18314  {
18315  TOneTrainFormattedEntry OneTTEntry;
18316  OneTTTrain.HeadCode = GetRepeatHeadCode(24, FirstHeadCode, y, IncDigits);
18317  TActionVectorEntry ActionVectorEntry = ActionVector.at(z);
18318  AnsiString PartStr = "", TimeStr = "";
18319 /*
18320  enum TTimetableFormatType {NoFormat, TimeLoc, TimeTimeLoc, TimeCmd, StartNew, TimeCmdHeadCode, FinRemHere,
18321  FNSNonRepeatToShuttle, SNTShuttle, SNSShuttle, SNSNonRepeatFromShuttle, FSHNewService, Repeat, PassTime,
18322  ExitRailway};
18323  enum TTimetableSequenceType {NoSequence, StartSequence, FinishSequence, IntermediateSequence, SequTypeForRepeatEntry};
18324  enum TTimetableLocationType {NoLocation, AtLocation, EnRoute, LocTypeForRepeatEntry};
18325  enum TTimetableShuttleLinkType {NoShuttleLink, NotAShuttleLink, ShuttleLink, ShuttleLinkTypeForRepeatEntry};
18326 */
18327  if(ActionVectorEntry.SequenceType == StartSequence)
18328  {
18329  if(ActionVectorEntry.FormatType == StartNew)
18330  {
18331  if(ActionVectorEntry.LocationName != "")
18332  {
18333  if(Track->TrackElementAt(742, ActionVectorEntry.RearStartOrRepeatMins).TrackType == Continuation)
18334  {
18335  PartStr = "Enters at " + ActionVectorEntry.LocationName;
18336  }
18337  else
18338  {
18339  PartStr = "Created at " + ActionVectorEntry.LocationName;
18340  }
18341  }
18342  else // may be a named continuation or other element, and if so report that
18343  {
18344  AnsiString LocName = Track->TrackElementAt(739, ActionVectorEntry.RearStartOrRepeatMins).ActiveTrackElementName;
18345  if(Track->TrackElementAt(740, ActionVectorEntry.RearStartOrRepeatMins).TrackType == Continuation)
18346  {
18347  if(LocName != "")
18348  {
18349  PartStr = "Enters at " + LocName;
18350  }
18351  else // use rear position if it's a continuation
18352  {
18353  PartStr = "Enters at " + Track->TrackElementAt(737, ActionVectorEntry.RearStartOrRepeatMins).ElementID;
18354  }
18355  }
18356  else // not a continuation
18357  {
18358  if(LocName != "")
18359  // if not a continuation then LocName should be same as ActionVectorEntry.LocationName
18360  // but include anyway
18361  {
18362  PartStr = "Created at " + LocName;
18363  }
18364  else // use rear position again
18365  {
18366  PartStr = "Created at " + Track->TrackElementAt(741, ActionVectorEntry.RearStartOrRepeatMins).ElementID;
18367  }
18368  }
18369  }
18370  TimeStr = Utilities->Format96HHMM(GetRepeatTime(20, ActionVectorEntry.EventTime, y, IncMinutes));
18371  }
18372  else if(ActionVectorEntry.FormatType == SNTShuttle)
18373  {
18374  if(y == 0) // first train
18375  {
18376  PartStr = "Enters at " + ActionVectorEntry.LocationName;
18377  TimeStr = Utilities->Format96HHMM(GetRepeatTime(21, ActionVectorEntry.EventTime, y, IncMinutes));
18378  }
18379  else
18380  {
18381  PartStr = "Repeat shuttle service at " + ActionVectorEntry.LocationName + " from ";
18382  TimeStr = GetRepeatHeadCode(45, ActionVectorEntry.OtherHeadCode, y - 1, IncDigits) + " at " +
18383  Utilities->Format96HHMM(GetRepeatTime(26, ActionVectorEntry.EventTime, y, IncMinutes));
18384  } // y-1 for headcode above since it is the last repeat value that the train is from
18385 
18386  }
18387  else if(ActionVectorEntry.Command == "Sfs")
18388  {
18389  PartStr = "New service at " + ActionVectorEntry.LocationName + " split from";
18390  TimeStr = GetRepeatHeadCode(33, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18391  Utilities->Format96HHMM(GetRepeatTime(24, ActionVectorEntry.EventTime, y, IncMinutes));
18392  }
18393  else if(ActionVectorEntry.Command == "Sns")
18394  {
18395  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
18396  TimeStr = GetRepeatHeadCode(34, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18397  Utilities->Format96HHMM(GetRepeatTime(25, ActionVectorEntry.EventTime, y, IncMinutes));
18398  }
18399  else if(ActionVectorEntry.FormatType == SNSShuttle)
18400  {
18401  if(y == 0) // first entry from shuttle
18402  {
18403  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
18404  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " +
18405  Utilities->Format96HHMM(GetRepeatTime(27, ActionVectorEntry.EventTime, y, IncMinutes));
18406  }
18407  else
18408  {
18409  PartStr = "Repeat shuttle service at " + ActionVectorEntry.LocationName + " from ";
18410  TimeStr = GetRepeatHeadCode(35, ActionVectorEntry.OtherHeadCode, y - 1, IncDigits) + " at " +
18411  Utilities->Format96HHMM(GetRepeatTime(22, ActionVectorEntry.EventTime, y, IncMinutes));
18412  } // y-1 for headcode above since it is the last repeat value that the train is from
18413 
18414  }
18415  else if(ActionVectorEntry.FormatType == SNSNonRepeatFromShuttle)
18416  {
18417  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
18418  // need repeat for the non-repeating headcode as it's the last train of the repeating shuttle
18419  TTrainDataEntry *TDE = ActionVectorEntry.LinkedTrainEntryPtr;
18420  AnsiString FirstHeadCode = TDE->HeadCode;
18421  int LastRepeatNumber = TDE->NumberOfTrains - 1;
18422  // a shuttle has to have at least 1 repeat
18423  int IncrementalDigits = TDE->ActionVector.at(TDE->ActionVector.size() - 1).FrontStartOrRepeatDigits;
18424  TimeStr = GetRepeatHeadCode(36, FirstHeadCode, LastRepeatNumber, IncrementalDigits) + " at " +
18425  Utilities->Format96HHMM(GetRepeatTime(23, ActionVectorEntry.EventTime, y, IncMinutes));
18426  }
18427  }
18428  else if(ActionVectorEntry.SequenceType == IntermediateSequence)
18429  {
18430  if(ActionVectorEntry.FormatType == TimeTimeLoc)
18431  {
18432  // here need 2 entries if times different so push the first right away & the second later
18433  // if times same just give the arrival entry
18434  if(ActionVectorEntry.DepartureTime != ActionVectorEntry.ArrivalTime)
18435  {
18436  PartStr = "Arrives at " + ActionVectorEntry.LocationName;
18437  TimeStr = Utilities->Format96HHMM(GetRepeatTime(4, ActionVectorEntry.ArrivalTime, y, IncMinutes));
18438  OneTTEntry.Action = PartStr;
18439  OneTTEntry.Time = TimeStr;
18440  OneTTTrain.OneFormattedTrainVector.push_back(OneTTEntry);
18441  PartStr = "Departs from " + ActionVectorEntry.LocationName;
18442  TimeStr = Utilities->Format96HHMM(GetRepeatTime(5, ActionVectorEntry.DepartureTime, y, IncMinutes));
18443  }
18444  else
18445  {
18446  PartStr = "Arrives & departs " + ActionVectorEntry.LocationName;
18447  TimeStr = Utilities->Format96HHMM(GetRepeatTime(29, ActionVectorEntry.ArrivalTime, y, IncMinutes));
18448  }
18449  }
18450  else if((ActionVectorEntry.FormatType == TimeLoc) && (ActionVectorEntry.ArrivalTime != TDateTime(-1)))
18451  {
18452  PartStr = "Arrives at " + ActionVectorEntry.LocationName;
18453  TimeStr = Utilities->Format96HHMM(GetRepeatTime(6, ActionVectorEntry.ArrivalTime, y, IncMinutes));
18454  }
18455  else if((ActionVectorEntry.FormatType == TimeLoc) && (ActionVectorEntry.ArrivalTime == TDateTime(-1)))
18456  {
18457  PartStr = "Departs from " + ActionVectorEntry.LocationName;
18458  TimeStr = Utilities->Format96HHMM(GetRepeatTime(7, ActionVectorEntry.DepartureTime, y, IncMinutes));
18459  }
18460  else if(ActionVectorEntry.FormatType == PassTime)
18461  {
18462  PartStr = "Passes " + ActionVectorEntry.LocationName;
18463  TimeStr = Utilities->Format96HHMM(GetRepeatTime(8, ActionVectorEntry.EventTime, y, IncMinutes));
18464  }
18465  else if(ActionVectorEntry.Command == "jbo")
18466  {
18467  PartStr = "Joined at " + ActionVectorEntry.LocationName + " by";
18468  TimeStr = GetRepeatHeadCode(37, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18469  Utilities->Format96HHMM(GetRepeatTime(9, ActionVectorEntry.EventTime, y, IncMinutes));
18470  }
18471  else if(ActionVectorEntry.Command == "fsp")
18472  {
18473  if(ActionVectorEntry.SplitDistribution != "") //new at v2.15.0
18474  {
18475  PartStr = "Splits from front [mass%-power% = " + ActionVectorEntry.SplitDistribution + "] at " + ActionVectorEntry.LocationName + " to form";
18476  }
18477  else
18478  {
18479  PartStr = "Splits from front [mass%-power% = 50-50] at " + ActionVectorEntry.LocationName + " to form";
18480  }
18481  TimeStr = GetRepeatHeadCode(38, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18482  Utilities->Format96HHMM(GetRepeatTime(10, ActionVectorEntry.EventTime, y, IncMinutes));
18483  }
18484  else if(ActionVectorEntry.Command == "rsp")
18485  {
18486  if(ActionVectorEntry.SplitDistribution != "") //new at v2.15.0
18487  {
18488  PartStr = "Splits from front [mass%-power% = " + ActionVectorEntry.SplitDistribution + "] at " + ActionVectorEntry.LocationName + " to form";
18489  }
18490  else
18491  {
18492  PartStr = "Splits from front [mass%-power% = 50-50] at " + ActionVectorEntry.LocationName + " to form";
18493  }
18494  TimeStr = GetRepeatHeadCode(39, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18495  Utilities->Format96HHMM(GetRepeatTime(11, ActionVectorEntry.EventTime, y, IncMinutes));
18496  }
18497  else if(ActionVectorEntry.Command == "cdt")
18498  {
18499  PartStr = "Changes direction at " + ActionVectorEntry.LocationName;
18500  TimeStr = Utilities->Format96HHMM(GetRepeatTime(12, ActionVectorEntry.EventTime, y, IncMinutes));
18501  }
18502  else if(ActionVectorEntry.Command == "dsc")
18503  {
18504  PartStr = "Changes description at " + ActionVectorEntry.LocationName;
18505  TimeStr = Utilities->Format96HHMM(GetRepeatTime(76, ActionVectorEntry.EventTime, y, IncMinutes));
18506  }
18507  else if(ActionVectorEntry.Command == "cms")
18508  {
18509  PartStr = "Change maximum speed to " + ActionVectorEntry.NewMaxSpeed + " at " + ActionVectorEntry.LocationName;
18510  TimeStr = Utilities->Format96HHMM(GetRepeatTime(78, ActionVectorEntry.EventTime, y, IncMinutes));
18511  }
18512  }
18513  else if(ActionVectorEntry.SequenceType == FinishSequence)
18514  {
18515  if(ActionVectorEntry.Command == "Fns")
18516  {
18517  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18518  TimeStr = GetRepeatHeadCode(40, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18519  Utilities->Format96HHMM(GetRepeatTime(13, ActionVectorEntry.EventTime, y, IncMinutes));
18520  }
18521  else if(ActionVectorEntry.Command == "F-nshs")
18522  {
18523  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18524  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " + Utilities->Format96HHMM
18525  (GetRepeatTime(17, ActionVectorEntry.EventTime, y, IncMinutes));
18526  }
18527  else if((ActionVectorEntry.Command == "Fns-sh") && (y < (TrainDataEntry.NumberOfTrains - 1)))
18528  {
18529  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service ";
18530  TimeStr = GetRepeatHeadCode(41, ActionVectorEntry.OtherHeadCode, y + 1, IncDigits) + " at " +
18531  Utilities->Format96HHMM(GetRepeatTime(14, ActionVectorEntry.EventTime, y, IncMinutes));
18532  // y+1 because it's the NEXT service repeat number that is relevant
18533  }
18534  else if((ActionVectorEntry.Command == "Fns-sh") && (y >= (TrainDataEntry.NumberOfTrains - 1)))
18535  {
18536  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18537  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " + Utilities->Format96HHMM
18538  (GetRepeatTime(15, ActionVectorEntry.EventTime, y, IncMinutes));
18539  }
18540  else if((ActionVectorEntry.Command == "Frh-sh") && (y < (TrainDataEntry.NumberOfTrains - 1)))
18541  {
18542  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18543  TimeStr = GetRepeatHeadCode(43, ActionVectorEntry.OtherHeadCode, y + 1, IncDigits) + " at " +
18544  Utilities->Format96HHMM(GetRepeatTime(16, ActionVectorEntry.EventTime, y, IncMinutes));
18545  // y+1 because it's the NEXT service repeat number that is relevant
18546  }
18547  else if((ActionVectorEntry.Command == "Frh-sh") && (y >= (TrainDataEntry.NumberOfTrains - 1)))
18548  {
18549  PartStr = "Terminates shuttle service at " + ActionVectorEntry.LocationName;
18550  // only used in chronological tt
18551  TimeStr = "End at " + Utilities->Format96HHMM(GetRepeatTime(28, ActionVectorEntry.EventTime, y, IncMinutes));
18552  // the "End at " is stripped out of the chronological tt but displayed in the traditional tt
18553  }
18554  else if(ActionVectorEntry.Command == "Frh")
18555  {
18556  PartStr = "Terminates at " + ActionVectorEntry.LocationName;
18557  // need here to examine the time of the preceding entry, may be ArrivalTime if TimeLoc, or EventTime otherwise
18558  if(z > 0)
18559  // should be for finish entry but include check for safety
18560  {
18561  if(ActionVector.at(z - 1).EventTime != TDateTime(-1))
18562  {
18563  TimeStr = Utilities->Format96HHMM(GetRepeatTime(30, ActionVector.at(z - 1).EventTime, y, IncMinutes));
18564  }
18565  else if(ActionVector.at(z - 1).ArrivalTime != TDateTime(-1))
18566  {
18567  TimeStr = Utilities->Format96HHMM(GetRepeatTime(31, ActionVector.at(z - 1).ArrivalTime, y, IncMinutes));
18568  }
18569  else
18570  {
18571  TimeStr = " "; // shouldn't ever get here
18572  }
18573  }
18574  }
18575  else if(ActionVectorEntry.Command == "Fer")
18576  {
18577  AnsiString AllowedExits;
18578  PartStr = "Exits railway" + GetExitLocationAndAt(0, ActionVectorEntry.ExitList, AllowedExits) + AllowedExits;
18579  TimeStr = Utilities->Format96HHMM(GetRepeatTime(18, ActionVectorEntry.EventTime, y, IncMinutes));
18580  }
18581  else if(ActionVectorEntry.Command == "Fjo")
18582  {
18583  PartStr = "At " + ActionVectorEntry.LocationName + " joins";
18584  TimeStr = GetRepeatHeadCode(44, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18585  Utilities->Format96HHMM(GetRepeatTime(19, ActionVectorEntry.EventTime, y, IncMinutes));
18586  }
18587  }
18588  else if(ActionVectorEntry.SequenceType == SequTypeForRepeatEntry)
18589  {
18590  continue; // no entry needed for a repeat
18591  }
18592  OneTTEntry.Action = PartStr;
18593  OneTTEntry.Time = TimeStr;
18594  OneTTTrain.OneFormattedTrainVector.push_back(OneTTEntry);
18595  // one per action
18596  }
18597  OneTTLine.OneCompleteFormattedTrainVector.push_back(OneTTTrain);
18598  // one per repeat
18599  }
18600  AllTTTrains->push_back(OneTTLine); // one per repeating train
18601  }
18602  // AllTTTrains vector now complete
18603 
18604  std::ofstream TTFile(TTFileName.c_str()); //formatted timetable
18605 
18606  if(TTFile == 0)
18607  {
18608  StopTTClockMessage(64, "Formatted timetable file failed to open - can't be created");
18609  delete AllTTTrains;
18610  Utilities->CallLogPop(1567);
18611  return;
18612  }
18613 /* formatted timetable types
18614  class TOneTrainFormattedEntry
18615  {
18616  AnsiString Action;//includes location if relevant
18617  AnsiString Time;
18618  };
18619 
18620  typedef std::vector<TOneTrainFormattedEntry> TOneFormattedTrainVector;
18621 
18622  class TOneCompleteFormattedTrain//headcode + list of actions
18623  {
18624  public:
18625  AnsiString HeadCode;
18626  TOneFormattedTrainVector OneFormattedTrainVector;
18627  };
18628 
18629  typedef std::vector<TOneCompleteFormattedTrain> TOneCompleteFormattedTrainVector;//list af all repeats
18630 
18631  class TTrainFormattedInformation//contains all information for a single TT entry (including repeats)
18632  {
18633  public:
18634  AnsiString Header;//description, mass, power, brake rate etc
18635  int NumberOfTrains;// number of repeats + 1
18636  TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector;//list af all repeats
18637  };
18638 
18639  typedef std::vector<TTrainFormattedInformation> TAllFormattedTrains;//all timetable in formatted form
18640  //end of formatted timetable types
18641 */
18642 
18643  // new layout using multiple rows
18644  TTFile << TableTitle.c_str() << '\n' << '\n';
18645  for(unsigned int x = 0; x < AllTTTrains->size(); x++)
18646  {
18647  TTFile << AllTTTrains->at(x).Header.c_str();
18648  TTFile << '\n';
18649  TTFile << ','; // for the blank line above the action list
18650  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
18651  {
18652  if(y < (AllTTTrains->at(x).NumberOfTrains - 1))
18653  {
18654  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode.c_str() << ',';
18655  }
18656  else
18657  {
18658  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode.c_str();
18659  }
18660  }
18661  TTFile << '\n' << '\n';
18662 
18663  for(unsigned int z = 0; z < AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(0).OneFormattedTrainVector.size(); z++)
18664  {
18665  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(0).OneFormattedTrainVector.at(z).Action.c_str() << ',';
18666  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
18667  {
18668  if(y < (AllTTTrains->at(x).NumberOfTrains - 1))
18669  {
18670  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time.c_str() << ',';
18671  }
18672  else
18673  {
18674  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time.c_str();
18675  }
18676  }
18677  TTFile << '\n';
18678  }
18679  TTFile << '\n' << '\n';
18680  }
18681 
18682  TTFile.close();
18683 
18684  AnsiString TTFileName2 = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
18685 
18686  TTFileName2 = CurDir + "\\Formatted timetables\\Timetable " + TTFileName2 + "; " + RailwayTitle + "; " + TimetableTitle + ".txt";
18687 
18688  std::ofstream TTFile2(TTFileName2.c_str()); //chronological timetable
18689 
18690  if(TTFile2 == 0)
18691  {
18692  StopTTClockMessage(67, "Chronological timetable file failed to open - can't be created");
18693  delete AllTTTrains;
18694  Utilities->CallLogPop(1710);
18695  return;
18696  }
18697  typedef std::multimap<AnsiString, AnsiString>TAnsiMultiMap;
18698  std::multimap<AnsiString, AnsiString>::iterator AMMIT;
18699  std::pair<AnsiString, AnsiString>AnsiMultiMapEntry;
18700 
18701  TAnsiMultiMap *TAMM = new TAnsiMultiMap;
18702  LastTTTime = ""; //records the very last time in the timetable - used in analysis file for Frh entries
18703 
18704  // multimap of AnsiStrings with TimeString as key (to sort automatically)
18705 
18706  TTFile2 << TableTitle.c_str() << '\n' << '\n';
18707  for(unsigned int x = 0; x < AllTTTrains->size(); x++)
18708  {
18709  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
18710  {
18711  for(unsigned int z = 0; z < AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.size(); z++)
18712  {
18713  bool GiveMessagesFalse = false;
18714  AnsiString TimeString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time;
18715  AnsiString HeadCodeString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode;
18716  AnsiString ActionString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Action;
18717  if(CheckHeadCodeValidity(11, GiveMessagesFalse, TimeString.SubString(1, 4)))
18718  // 'NXNN at HH:MM' (will return true if H/C as integ check passed)
18719  {
18720  // fails for HH:MM because of ':' or 'End at HH:MM' because of ' '
18721  AnsiString OtherHeadCode = TimeString.SubString(1, 4);
18722  TimeString = TimeString.SubString(9, 5);
18723  ActionString += " " + OtherHeadCode;
18724  }
18725  if(TimeString.SubString(1, 7) == "End at ")
18726  // for Frh-sh final entry
18727  {
18728  TimeString = TimeString.SubString(8, 5);
18729  }
18730  AnsiString OneLine = TimeString + ' ' + HeadCodeString + ' ' + ActionString + '\n';
18731  AnsiMultiMapEntry.first = TimeString;
18732  AnsiMultiMapEntry.second = OneLine;
18733  TAMM->insert(AnsiMultiMapEntry);
18734  }
18735  }
18736  }
18737 
18738  for(AMMIT = TAMM->begin(); AMMIT != TAMM->end(); AMMIT++)
18739  {
18740  TTFile2 << (AMMIT->second).c_str();
18741  }
18742  delete AllTTTrains;
18743  delete TAMM;
18744  TTFile2.close();
18745  Utilities->CallLogPop(1580);
18746 }
18747 
18748 // ---------------------------------------------------------------------------
18749 
18750 bool TTrainController::CreateTTAnalysisFile(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir, bool ArrChecked, bool DepChecked,
18751  bool AtLocChecked, bool DirChecked, int ArrRange, int DepRange)
18752 {
18753 
18754  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CreateTTAnalysisFile");
18755  bool AnalysisError = false;
18756  AnsiString SequenceLog = "SequenceLog\n";
18757 
18758 /* Double crosslink (shuttle) table:
18759 
18760 Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
18761  Code ShuttleLink- EntryPtr ShuttleLink-
18762  HeadCode EntryPtr
18763 
18764 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
18765 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
18766 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
18767 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
18768 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
18769 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
18770 
18771 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
18772 */
18773 
18774  try
18775  {
18776  //New section at v2.5.0 for tt conflict analysis
18777  /*
18778  typedef std::list<AnsiString> TServiceCallingLocsList;
18779  typedef std::map<AnsiString, TServiceCallingLocsList> TAllServiceCallingLocsMap;
18780 
18782  struct TLocServiceTimes
18783  {
18784  AnsiString Location;
18785  AnsiString ServiceAndRepeatNum;
18786  AnsiString AtLocTime;
18787  AnsiString ArrTime;
18788  AnsiString DepTime;
18789  AnsiString FrhMarker;
18790  };
18791  typedef std::vector<TLocServiceTimes> TLocServiceTimesVector;
18792  */
18793 
18794  //first have to check through all the services and give each one a unique name, or the analysis won't recognise differences between services that have the same reference
18795  //to do that need a new TrainDataVector as don't want to change anything in the original. TrainDataVectorCopy is used for building AllServiceCallingLocsMap & LocServiceTimesVector
18796 
18797 //create TrainDataVectorCopy and populate service refs with /1, /2 etc
18798  TrainDataVectorCopy = TrainDataVector; //don't need it on heap as TrainController is on the heap. Didn't need others in CreatFormattedTimetables but leave as is.
18799  TTrainDataVector::iterator TDVIt, TDVCopyIt;
18800  int Suffix = 0;
18801  int IteratorNumber = 0;
18802  AnsiString AnsiSuffix = "";
18803  for(TDVIt = TrainDataVector.begin(); TDVIt != TrainDataVector.end() - 1; TDVIt++)
18804  {
18805  IteratorNumber++; //first value in loop is 1
18806  Suffix = 0;
18807  for(TDVCopyIt = TrainDataVectorCopy.begin() + IteratorNumber; TDVCopyIt != TrainDataVectorCopy.end(); TDVCopyIt++)
18808  {
18809  if(TDVCopyIt->HeadCode == TDVIt->HeadCode)
18810  {
18811  Suffix++; //first value is 1
18812  AnsiSuffix = AnsiString(Suffix);
18813  TDVCopyIt->ServiceReference = TDVIt->HeadCode + "/" + AnsiSuffix; //set both the HeadCode + any forward slashes and numbers, this is because sometimes
18814  TDVCopyIt->HeadCode = TDVIt->HeadCode + "/" + AnsiSuffix; //service refs are used and sometimes H/Cs, so need them to be the same,
18815  } //they are all unique at this point anyway
18816  }
18817  }
18818 //now make all linked pointers in ActionVectorEntries point to links in the vector copy (still point to original vector at this stage)
18819 //and set the linked headcodes to the correct values - all unique at this point
18820  int Increment = 0, SlashPos;
18821  TActionVectorIterator AVEIt;
18822  AnsiString LinkedHeadCode;
18823 
18824  for(TDVCopyIt = TrainDataVectorCopy.begin(); TDVCopyIt != TrainDataVectorCopy.end(); TDVCopyIt++)
18825  {
18826  for(AVEIt = TDVCopyIt->ActionVector.begin(); AVEIt != TDVCopyIt->ActionVector.end(); AVEIt++)
18827  {
18828  if(AVEIt->LinkedTrainEntryPtr != NULL)
18829  {
18830  Increment = AVEIt->LinkedTrainEntryPtr - &TrainDataVector.at(0);
18831  AVEIt->LinkedTrainEntryPtr = &TrainDataVectorCopy.at(0) + Increment;
18832  //now set AVEIt->OtherHeadCode to the linked headcodes with /1, /2 etc
18833  //but note that these ARE HeadCodes and not service refs, so need to strip off any prefixes from the linked service refs
18834  LinkedHeadCode = (*AVEIt->LinkedTrainEntryPtr).ServiceReference;
18835  //now count from back until reach a '/' character or a non-integer character, if reach non-integer first then no '/' present at end of H/c (but may be one earlier as a prefix)
18836  SlashPos = 0;
18837  for(int x = LinkedHeadCode.Length(); x > 0; x--)
18838  {
18839  if(LinkedHeadCode[x] == '/')
18840  {
18841  SlashPos = LinkedHeadCode.Length() - x + 1;
18842  break;
18843  }
18844  else if((LinkedHeadCode[x] != '0') && (LinkedHeadCode[x] != '1') && (LinkedHeadCode[x] != '2') && (LinkedHeadCode[x] != '3') &&
18845  (LinkedHeadCode[x] != '4') && (LinkedHeadCode[x] != '5') && (LinkedHeadCode[x] != '6') && (LinkedHeadCode[x] != '7') &&
18846  (LinkedHeadCode[x] != '8') && (LinkedHeadCode[x] != '8'))
18847  {
18848  break;
18849  }
18850  }
18851  //now strip off any prefix
18852  AVEIt->OtherHeadCode = LinkedHeadCode.SubString(LinkedHeadCode.Length() - 3 - SlashPos, 4 + SlashPos);
18853  }
18854  else
18855  {
18856  AVEIt->OtherHeadCode = "";
18857  }
18858  if(AVEIt->NonRepeatingShuttleLinkEntryPtr != NULL)
18859  {
18860  Increment = AVEIt->NonRepeatingShuttleLinkEntryPtr - &TrainDataVector.at(0);
18861  AVEIt->NonRepeatingShuttleLinkEntryPtr = &TrainDataVectorCopy.at(0) + Increment;
18862  //now set AVEIt->NonRepeatingShuttleLinkHeadCode to the linked headcodes with /1, /2 etc
18863  //but note that these ARE HeadCodes and not service refs, so need to strip off any prefixes from the linked service refs
18864  LinkedHeadCode = (*AVEIt->NonRepeatingShuttleLinkEntryPtr).ServiceReference;
18865  //now count from back until reach a '/' character or a non-integer character, if reach non-integer first then no '/' present at end of H/c (but may be one earlier as a prefix)
18866  SlashPos = 0;
18867  for(int x = LinkedHeadCode.Length(); x > 0; x--)
18868  {
18869  if(LinkedHeadCode[x] == '/')
18870  {
18871  SlashPos = LinkedHeadCode.Length() - x + 1;
18872  break;
18873  }
18874  else if((LinkedHeadCode[x] != '0') && (LinkedHeadCode[x] != '1') && (LinkedHeadCode[x] != '2') && (LinkedHeadCode[x] != '3') &&
18875  (LinkedHeadCode[x] != '4') && (LinkedHeadCode[x] != '5') && (LinkedHeadCode[x] != '6') && (LinkedHeadCode[x] != '7') &&
18876  (LinkedHeadCode[x] != '8') && (LinkedHeadCode[x] != '8'))
18877  {
18878  break;
18879  }
18880  }
18881  //now strip off any prefix
18882  AVEIt->NonRepeatingShuttleLinkHeadCode = LinkedHeadCode.SubString(LinkedHeadCode.Length() - 3 - SlashPos, 4 + SlashPos);
18883  }
18884  else
18885  {
18886  AVEIt->NonRepeatingShuttleLinkHeadCode = "";
18887  }
18888  }
18889  }
18890  //from here only TrainDataVectorCopy used
18891  SequenceLog += "1\n";
18892  //build AllServiceCallingLocsMap, it only uses the base service reference (with /1, /2 etc suffixes) as later times are calculated from the repeat number
18893  TServiceCallingLocsList ServiceCallingLocsList;
18894  std::pair<AnsiString, TServiceCallingLocsList> AllServiceCallingLocsEntry;
18895  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
18896  {
18897  const TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
18898  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
18899  AllServiceCallingLocsEntry.first = TrainDataEntry.ServiceReference;
18900  ServiceCallingLocsList.clear();
18901  if(ActionVector.empty())
18902  {
18903  continue;
18904  }
18905  if(ActionVector.at(0).SignallerControl)
18906  {
18907  continue;
18908  }
18909  for(unsigned int z = 0; z < ActionVector.size(); z++)
18910  {
18911  TActionVectorEntry AVE = ActionVector.at(z);
18912  if(AVE.FormatType == StartNew)
18913  {
18914  if(AVE.LocationType == AtLocation) //located Snt
18915  {
18916  ServiceCallingLocsList.push_back(AVE.LocationName);
18917  }
18918  else //unlocated Snt (could be entering at continuation)
18919  {
18921  if(TE.ActiveTrackElementName != "")
18922  {
18923  ServiceCallingLocsList.push_back(TE.ActiveTrackElementName);
18924  }
18925  else
18926  {
18927  int HLoc = TE.HLoc;
18928  int VLoc = TE.VLoc;
18929  AnsiString HString;
18930  AnsiString VString;
18931  if(HLoc < 0)
18932  {
18933  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18934  }
18935  else
18936  {
18937  HString = AnsiString(HLoc);
18938  }
18939  if(VLoc < 0)
18940  {
18941  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18942  }
18943  else
18944  {
18945  VString = AnsiString(VLoc);
18946  }
18947  ServiceCallingLocsList.push_back(HString + '-' + VString);
18948  }
18949  }
18950  }
18951  else if(AVE.SequenceType == StartSequence) //other start entries, all located
18952  {
18953  ServiceCallingLocsList.push_back(AVE.LocationName);
18954  }
18955  else if(AVE.FormatType == TimeLoc) //z must be > 0
18956  {
18957  if(ServiceCallingLocsList.back() != AVE.LocationName)
18958  {
18959  ServiceCallingLocsList.push_back(AVE.LocationName); //may be listed twice in succession so only want one entry
18960  }
18961  }
18962  else if(AVE.FormatType == PassTime)
18963  {
18964  ServiceCallingLocsList.push_back(AVE.LocationName);
18965  }
18966  else if(AVE.FormatType == TimeTimeLoc)
18967  {
18968  ServiceCallingLocsList.push_back(AVE.LocationName);
18969  }
18970  else if(AVE.Command == "cdt") //list if not next to start or finish
18971  {
18972  if(ActionVector.at(z-1).SequenceType == StartSequence)
18973  {
18974  continue;
18975  }
18976  else if(ActionVector.at(z+1).SequenceType == FinishSequence) //although deal with Fer entries cdt (train stopped) can't precede FER (train moving)
18977  {
18978  continue;
18979  }
18980  else
18981  {
18982  AnsiString TimeString = Utilities->Format96HHMM(AVE.EventTime);
18983  ServiceCallingLocsList.push_back("%%%" + TimeString); //%%% is a marker - unlikely that any locations will begin with this & easy to check to identify a time
18984  }
18985  }
18986  else if(AVE.FormatType == ExitRailway) //Fer
18987  {
18988  TTrackElement TE = Track->TrackElementAt(995, AVE.ExitList.front());
18989  AnsiString LName = TE.ActiveTrackElementName;
18990  if(LName != "")
18991  {
18992  ServiceCallingLocsList.push_back(LName);
18993  }
18994  else
18995  {
18996  int HLoc = TE.HLoc;
18997  int VLoc = TE.VLoc;
18998  AnsiString HString;
18999  AnsiString VString;
19000  if(HLoc < 0)
19001  {
19002  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
19003  }
19004  else
19005  {
19006  HString = AnsiString(HLoc);
19007  }
19008  if(VLoc < 0)
19009  {
19010  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
19011  }
19012  else
19013  {
19014  VString = AnsiString(VLoc);
19015  }
19016  ServiceCallingLocsList.push_back(HString + '-' + VString);
19017  }
19018  }
19019  }
19020  AllServiceCallingLocsEntry.second = ServiceCallingLocsList;
19021  AllServiceCallingLocsMap.insert(AllServiceCallingLocsEntry);
19022  }
19023  //AllServiceCallingLocsMap built
19024  SequenceLog += "2\n";
19025 /*
19026 // this sequence is to test the validity of AllServiceCallingLocsMap
19027  AnsiString TestFile = CurDir + "\\Formatted timetables\\TestFile; " + RailwayTitle + "; " + TimetableTitle + ".txt";
19028  std::ofstream Test(TestFile.c_str());
19029 
19030  if(TestFile == 0)
19031  {
19032  ShowMessage("TestFile failed to open - can't be created");
19033  Utilities->CallLogPop();
19034  return false;
19035  }
19036 
19037  for(TAllServiceCallingLocsMap::iterator ASCLIt = AllServiceCallingLocsMap.begin(); ASCLIt != AllServiceCallingLocsMap.end(); ASCLIt++)
19038  {
19039  Test << ASCLIt->first << '\n'; //service ref
19040  for(TServiceCallingLocsList::iterator SCLIt = ASCLIt->second.begin(); SCLIt != ASCLIt->second.end(); SCLIt++)
19041  {
19042  Test << *SCLIt << '\n';
19043  }
19044  Test << "\n\n";
19045  }
19046  Test.close();
19047  Utilities->CallLogPop();
19048  return true;
19049 */
19050  //initialise variables before calc LastTTTime & build LocServiceTimesVector
19051  if(TrainDataVectorCopy.empty())
19052  {
19053  ShowMessage("Unable to create a program-readable timetable - please check the timetable file validity");
19054  Utilities->CallLogPop(2209);
19055  return(false);
19056  }
19057  TLocServiceTimes TLSTEntry;
19058  TLocServiceTimesVector LocServiceTimesVector; //will be on heap as TrainController is on the heap
19059  bool NumPlatsAtThisLocCalculated = false, ArrivalsPrinted = false, DeparturesPrinted = false, AtLocsPrinted = false;
19060  AnsiString PreviousService = "", PreviousServiceAndRepeatNumTotalOutput = "", BasicTime = "", MinuteString = "", LastAnsiTime = "";
19061  int NumTrains = 0, NumPlats = 0, LastFrhCount = 0, FrhCount = 0, NumTrainsAtLoc = 0;
19062  LastTTTime = "";
19063  SequenceLog += "3\n";
19064  //calculate LastTTTime
19065  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
19066  {
19067  TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
19068  TActionVector &ActionVector = TrainDataEntry.ActionVector;
19069  TActionVectorIterator AVLast = ActionVector.end() - 1; //points to last entry
19070  TDateTime LastTDTime;
19071  int IncMinutes = 0;
19072  NumTrains = TrainDataEntry.NumberOfTrains;
19073  if(ActionVector.empty())
19074  {
19075  continue;
19076  }
19077  if(ActionVector.at(0).SignallerControl)
19078  {
19079  continue;
19080  }
19081  if(AVLast->FormatType == Repeat)
19082  {
19083  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
19084  AVLast--; //now points to the command before the repeat
19085  }
19086  if(AVLast->FormatType == FinRemHere) //not 'else if' as may have both a repeat and an Frh
19087  {
19088  AVLast--; //points to last timed entry
19089  }
19090  //here AVLast points to last entry with a time
19091  if(AVLast->ArrivalTime != TDateTime(-1))
19092  {
19093  LastTDTime = AVLast->ArrivalTime;
19094  }
19095  else if(AVLast->EventTime != TDateTime(-1)) //can't be a departure time
19096  {
19097  LastTDTime = AVLast->EventTime;
19098  }
19099  else
19100  {
19101  continue; //shouldn't ever reach here but if do then skip this service
19102  }
19103  if(NumTrains == 1)
19104  {
19105  LastAnsiTime = Utilities->Format96HHMM(LastTDTime);
19106  }
19107  else
19108  {
19109  LastAnsiTime = Utilities->Format96HHMM(GetRepeatTime(59, LastTDTime, NumTrains - 1, IncMinutes));
19110  }
19111  if(LastAnsiTime > LastTTTime)
19112  {
19113  LastTTTime = LastAnsiTime;
19114  }
19115  }
19116  SequenceLog += "4\n";
19117 //build LocServiceTimesVector
19118 
19119 /*
19120  struct TLocServiceTimes
19121  {
19122  AnsiString Location;
19123  AnsiString ServiceAndRepeatNum;
19124  AnsiString AtLocTime;
19125  AnsiString ArrTime;
19126  AnsiString DepTime;
19127  AnsiString FrhMarker;
19128  };
19129  typedef std::vector<TLocServiceTimes> TLocServiceTimesVector;
19130 
19131 This works as follows:
19132 ServiceAndRepeatNum is taken from the TrainDataVectorCopy as it is the same for all actionvector entries
19133 Location is taken from ActionVectorEntry.LocationName if there is one, or from the H & V locations if not (e.g. at an unnamed Fer)
19134 AtLocTime is always entered either on its own or with ArrTime or DepTime as appropriate
19135 
19136 Every action for every train is examined and times entered as follows:-
19137 a) a located Snt: entry time becomes the AtLocTime, and all subsequent minutes entered too up to but not including a departure or a finish
19138 b) an unlocated Snt: entry time becomes DepTime
19139 c) all other start entries: entry time becomes AtLoc, and all subsequent minutes entered too up to but not including a departure or a finish
19140 d) TimeLoc Arr: entry time becomes ArrTime, and all subsequent minutes entered too up to but not including a departure or a finish
19141 e) TimeLoc Dep: entry time becomes DepTime, checks if DepTime same as earlier ArrTime and if so all times go in as one entry
19142 f) TimeTimeLoc: Arrival time entered as ArrTime, a check if Arr & Dep same and if so go in as one entry, else all minutes between entered as AtLocs then DepTime
19143 g) ExitRailway (Fer): check if located and use LocationName if so. else use H & V positions, time becomes AtLocTime
19144 h) Frh: use the earlier vector time as the AtLocTime and set FrhMarker, and enter all minutes to end of timetable as AtLocs
19145 i) Frh-sh: for the last train use time as AtLocTime, set FrhMarker, and enter all minutes to end of timetable as AtLocs
19146 j) all other finish entries (all link to another service) are ignored as will be listed for the linked service
19147 */
19148  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
19149  {
19150  const TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
19151  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
19152  AnsiString ServiceRef = TrainDataEntry.ServiceReference;
19153  int IncMinutes = 0;
19154  NumTrains = TrainDataEntry.NumberOfTrains;
19155  if(ActionVector.empty())
19156  {
19157  continue;
19158  }
19159  if(ActionVector.at(0).SignallerControl)
19160  {
19161  continue;
19162  }
19163  if(ActionVector.at(ActionVector.size() - 1).FormatType == Repeat)
19164  {
19165  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
19166  }
19167  for(int y = 0; y < NumTrains; y++) //y is the repeat number
19168  {
19169  if(NumTrains == 1)
19170  {
19171  TLSTEntry.ServiceAndRepeatNum = ServiceRef;
19172  }
19173  else if(y == 0)
19174  {
19175  TLSTEntry.ServiceAndRepeatNum = ServiceRef + " (First service)";
19176  }
19177  else
19178  {
19179  TLSTEntry.ServiceAndRepeatNum = ServiceRef + " (Repeat " + AnsiString(y) + ")";
19180  }
19181  for(unsigned int z = 0; z < ActionVector.size(); z++)
19182  {
19183  TActionVectorEntry AVE = ActionVector.at(z);
19184  TLSTEntry.AtLocTime = "";
19185  TLSTEntry.ArrTime = "";
19186  TLSTEntry.DepTime = "";
19187  TLSTEntry.Location = "";
19188  TLSTEntry.FrhMarker = "";
19189 
19190  if(AVE.FormatType == StartNew) //Snt only
19191  {
19192  if(AVE.LocationType == AtLocation) //located Snt, class time as AtLocTime
19193  {
19194  TLSTEntry.Location = AVE.LocationName;
19195  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(58, AVE.EventTime, y, IncMinutes));
19196  LocServiceTimesVector.push_back(TLSTEntry);
19197 
19198  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
19199  AnsiString IncTime = "", FoundStopTime = ""; //these handled in later checks
19200  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
19201  {
19202  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
19203  {
19204  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(62, ActionVector.at(a).DepartureTime, y, IncMinutes));
19205  break;
19206  }
19207  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered in a later test
19208  {
19209  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(63, ActionVector.at(a).EventTime, y, IncMinutes));
19210  break;
19211  }
19212  }
19213  if(FoundStopTime == "")
19214  {
19215  throw Exception("Failure to determine FoundStopTime for located Snt");
19216  }
19217  int WhileCount = 0;
19218  while(true)
19219  {
19220  //add minutes until reach FoundStopTime but don't add that time
19221  WhileCount++;
19222  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19223  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
19224  TLSTEntry.DepTime = "";
19225  TLSTEntry.ArrTime = "";
19226  if(IncTime >= FoundStopTime) //don't add that time
19227  {
19228  break;
19229  }
19230  LocServiceTimesVector.push_back(TLSTEntry);
19231  if(WhileCount > 2000)
19232  {
19233  throw Exception("While loop failed to break in 2000 loops for located Snt");
19234  }
19235  }
19236  }
19237  else //unlocated Snt, use the EventTime as DepTime for this vector
19238  {
19240  if(TE.ActiveTrackElementName != "")
19241  {
19242  TLSTEntry.Location = TE.ActiveTrackElementName;
19243  }
19244  else
19245  {
19246  int HLoc = TE.HLoc;
19247  int VLoc = TE.VLoc;
19248  AnsiString HString;
19249  AnsiString VString;
19250  if(HLoc < 0)
19251  {
19252  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
19253  }
19254  else
19255  {
19256  HString = AnsiString(HLoc);
19257  }
19258  if(VLoc < 0)
19259  {
19260  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
19261  }
19262  else
19263  {
19264  VString = AnsiString(VLoc);
19265  }
19266  TLSTEntry.Location = HString + '-' + VString;
19267  }
19268  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(49, AVE.EventTime, y, IncMinutes));
19269  TLSTEntry.AtLocTime = TLSTEntry.DepTime;
19270  LocServiceTimesVector.push_back(TLSTEntry);
19271  }
19272  }
19273 
19274  else if(AVE.SequenceType == StartSequence) //other start entries, all located
19275  {
19276  TLSTEntry.Location = AVE.LocationName;
19277  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(50, AVE.EventTime, y, IncMinutes));
19278  LocServiceTimesVector.push_back(TLSTEntry);
19279  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
19280  AnsiString IncTime = "", FoundStopTime = ""; //these handled in other checks
19281  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
19282  {
19283  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
19284  {
19285  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(64, ActionVector.at(a).DepartureTime, y, IncMinutes));
19286  break;
19287  }
19288  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered in a later test
19289  {
19290  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(65, ActionVector.at(a).EventTime, y, IncMinutes));
19291  break;
19292  }
19293  }
19294  if(FoundStopTime == "")
19295  {
19296  throw Exception("Failure to determine FoundStopTime for SequenceType == StartSequence");
19297  }
19298  int WhileCount = 0;
19299  while(true)
19300  {
19301  //add minutes until reach FoundStopTime but don't add that time
19302  WhileCount++;
19303  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19304  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
19305  TLSTEntry.DepTime = "";
19306  TLSTEntry.ArrTime = "";
19307  if(IncTime >= FoundStopTime) //don't add that time
19308  {
19309  break;
19310  }
19311  LocServiceTimesVector.push_back(TLSTEntry);
19312  if(WhileCount > 2000)
19313  {
19314  throw Exception("While loop failed to break in 2000 loops for SequenceType == StartSequence");
19315  }
19316  }
19317  }
19318 
19319  else if(AVE.FormatType == TimeLoc) //could be arr or dep, if arrival add in all mins to the departure or finish
19320  {
19321  TLSTEntry.Location = AVE.LocationName;
19322  if(AVE.ArrivalTime > TDateTime(-1)) //one or other set, not both, in this case arrival
19323  {
19324  bool SkipAddingMinutes = false;
19325  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(51, AVE.ArrivalTime, y, IncMinutes));
19326  TLSTEntry.AtLocTime = TLSTEntry.ArrTime;
19327  LocServiceTimesVector.push_back(TLSTEntry); //Arr and AtLoc added (may be popped if dep time found to be same at next TimeLoc)
19328  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
19329  AnsiString IncTime = "", FoundStopTime = ""; //these handled in other checks
19330  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
19331  {
19332  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
19333  {
19334  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(66, ActionVector.at(a).DepartureTime, y, IncMinutes));
19335  break;
19336  }
19337  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered for in a later test
19338  {
19339  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(67, ActionVector.at(a).EventTime, y, IncMinutes));
19340  if((a <= (z + 2)) && (FoundStopTime == TLSTEntry.ArrTime) && ((ActionVector.at(a).LinkedTrainEntryPtr != NULL) || (ActionVector.at(a).NonRepeatingShuttleLinkEntryPtr != NULL)))
19341  //finish immediately after arrival at same time, and a forward linked service. Added at v2.6.0 to prevent two linked trains being listed at same location
19342  //at v2.10.0 changed (a == z + 1) to (a <= (z + 2)) as can have a cdt between, this allows for that
19343  {
19344  LocServiceTimesVector.pop_back(); //pop the entry as the linked train will be listed at the relevant time and don't want to list both
19345  SkipAddingMinutes = true;
19346  }
19347  break;
19348  }
19349  }
19350  if(FoundStopTime == "")
19351  {
19352  throw Exception("Failure to determine FoundStopTime for SequenceType == StartSequence");
19353  }
19354  if(!SkipAddingMinutes)
19355  {
19356  int WhileCount = 0;
19357  while(true)
19358  {
19359  //add minutes until reach FoundStopTime but don't add that time
19360  WhileCount++;
19361  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19362  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
19363  TLSTEntry.DepTime = "";
19364  TLSTEntry.ArrTime = "";
19365  if(IncTime >= FoundStopTime) //don't add that time
19366  {
19367  break;
19368  }
19369  LocServiceTimesVector.push_back(TLSTEntry);
19370  if(WhileCount > 2000)
19371  {
19372  throw Exception("While loop failed to break in 2000 loops for SequenceType == StartSequence");
19373  }
19374  }
19375  }
19376  }
19377  else if(AVE.DepartureTime > TDateTime(-1)) //need to check if the arrival time (which should already be listed) is same and if so put all times on one line
19378  {
19379  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(52, AVE.DepartureTime, y, IncMinutes));
19380  TLSTEntry.AtLocTime = TLSTEntry.DepTime;
19381  if((TLSTEntry.Location == LocServiceTimesVector.back().Location) && (TLSTEntry.ServiceAndRepeatNum == LocServiceTimesVector.back().ServiceAndRepeatNum)) //if not it's a new service
19382  {
19383  if(TLSTEntry.DepTime == LocServiceTimesVector.back().ArrTime)
19384  {
19385  TLSTEntry.ArrTime = LocServiceTimesVector.back().ArrTime;
19386  LocServiceTimesVector.pop_back();
19387  LocServiceTimesVector.push_back(TLSTEntry); //Arr, Dep and AtLoc added in place of earlier Arr entry.
19388  }
19389  else //just add the dep & atloc times
19390  {
19391  TLSTEntry.ArrTime = "";
19392  LocServiceTimesVector.push_back(TLSTEntry);
19393  }
19394  }
19395  else //just add the dep & atloc times
19396  {
19397  TLSTEntry.ArrTime = "";
19398  LocServiceTimesVector.push_back(TLSTEntry);
19399  }
19400  }
19401  }
19402 
19403  else if(AVE.FormatType == TimeTimeLoc)
19404  {
19405  TLSTEntry.Location = AVE.LocationName;
19406  if(AVE.ArrivalTime > TDateTime(-1)) //should be
19407  {
19408  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(53, AVE.ArrivalTime, y, IncMinutes));
19409  TLSTEntry.AtLocTime = TLSTEntry.ArrTime;
19410  }
19411  if(AVE.DepartureTime > TDateTime(-1)) //should be
19412  {
19413  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(54, AVE.DepartureTime, y, IncMinutes));
19414  }
19415  if(TLSTEntry.ArrTime == TLSTEntry.DepTime)
19416  {
19417  LocServiceTimesVector.push_back(TLSTEntry);
19418  }
19419  else
19420  {
19421  AnsiString TempDepTime = TLSTEntry.DepTime; //save it temporarily
19422  TLSTEntry.DepTime = "";
19423  LocServiceTimesVector.push_back(TLSTEntry); //push just the arrival and AtLoc times
19424  TLSTEntry.ArrTime = ""; //done with this now
19425  while(TLSTEntry.AtLocTime < TempDepTime)
19426  {
19427  TLSTEntry.AtLocTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19428  if(TLSTEntry.AtLocTime == TempDepTime)
19429  {
19430  TLSTEntry.DepTime = TempDepTime; //restore value
19431  LocServiceTimesVector.push_back(TLSTEntry); //push the AtLoc and Dep times - will finish loop after this
19432  }
19433  else
19434  {
19435  LocServiceTimesVector.push_back(TLSTEntry); //push the AtLoc time on its own
19436  }
19437  }
19438  }
19439  }
19440 
19441  else if(AVE.FormatType == PassTime) //added at v2.9.1
19442  { //adds 2 entries, 1st with PassTime as ArrTime and AtLocTime, 2nd with PassTime as AtLocTime & DepTime
19443  TLSTEntry.Location = AVE.LocationName;;
19444  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(73, AVE.EventTime, y, IncMinutes));
19445  TLSTEntry.ArrTime = TLSTEntry.AtLocTime; //DepTime already set to null
19446  LocServiceTimesVector.push_back(TLSTEntry); //1st entry
19447  TLSTEntry.ArrTime = ""; //need to reset this to null
19448  TLSTEntry.DepTime = TLSTEntry.AtLocTime;
19449  LocServiceTimesVector.push_back(TLSTEntry); //2nd entry
19450  }
19451 
19452  else if(AVE.FormatType == ExitRailway) //Fer
19453  {
19454  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(77, AVE.EventTime, y, IncMinutes)); //need this as arrival time so arrival analysis works
19455  //properly for exits (continuation entries use dep time so ok)
19456  //added at v2.20.3
19457  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(55, AVE.EventTime, y, IncMinutes));
19458  //don't know which exit will be used during operation so use the first in ExitList, if several with different names then will
19459  //be wrong, but can't guess from here & most will have same name
19460  AnsiString LName = Track->TrackElementAt(990, AVE.ExitList.front()).ActiveTrackElementName;
19461  if(LName != "")
19462  {
19463  TLSTEntry.Location = LName;
19464  }
19465  else
19466  {
19467  int HLoc = Track->TrackElementAt(991, AVE.ExitList.front()).HLoc;
19468  int VLoc = Track->TrackElementAt(992, AVE.ExitList.front()).VLoc;
19469  AnsiString HString;
19470  AnsiString VString;
19471  if(HLoc < 0)
19472  {
19473  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
19474  }
19475  else
19476  {
19477  HString = AnsiString(HLoc);
19478  }
19479  if(VLoc < 0)
19480  {
19481  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
19482  }
19483  else
19484  {
19485  VString = AnsiString(VLoc);
19486  }
19487  TLSTEntry.Location = HString + '-' + VString;
19488  }
19489  LocServiceTimesVector.push_back(TLSTEntry); //just use the exit time as AtLocTime
19490  }
19491 
19492  else if(AVE.FormatType == FinRemHere) //Frh, not Frh-sh, that dealt with next
19493  {
19494  AnsiString FrhTime;
19495  if(ActionVector.at(z - 1).ArrivalTime != TDateTime(-1))
19496  {
19497  FrhTime = Utilities->Format96HHMM(GetRepeatTime(56, ActionVector.at(z - 1).ArrivalTime, y, IncMinutes));
19498  }
19499  else if(ActionVector.at(z - 1).EventTime != TDateTime(-1))
19500  {
19501  FrhTime = Utilities->Format96HHMM(GetRepeatTime(57, ActionVector.at(z - 1).EventTime, y, IncMinutes));
19502  }
19503  TLSTEntry.AtLocTime = FrhTime; //use the last entry time as the first recorded time
19504  TLSTEntry.Location = AVE.LocationName;
19505  AnsiString IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19506  TLSTEntry.FrhMarker = "Frh";
19507  LocServiceTimesVector.push_back(TLSTEntry);
19508  TLSTEntry.FrhMarker = "";
19509  //add all times from next minute to end of timetable
19510  while(IncTime <= LastTTTime)
19511  {
19512  TLSTEntry.AtLocTime = IncTime;
19513  LocServiceTimesVector.push_back(TLSTEntry);
19514  IncTime = Utilities->IncrementAnsiTimeOneMinute(IncTime);
19515  }
19516  }
19517 
19518  else if(AVE.Command == "Frh-sh") //do nothing if links to other shuttle but treat as Frh when remaining here
19519  {
19520  if(y == NumTrains - 1) //last repeat, it remains here when accessed for the last train
19521  {
19522  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(68, AVE.EventTime, y, IncMinutes));
19523  TLSTEntry.Location = AVE.LocationName;
19524  AnsiString IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19525  TLSTEntry.FrhMarker = "Frh";
19526  LocServiceTimesVector.push_back(TLSTEntry);
19527  TLSTEntry.FrhMarker = "";
19528  //add all times from next minute to end of timetable
19529  while(IncTime <= LastTTTime)
19530  {
19531  TLSTEntry.AtLocTime = IncTime;
19532  LocServiceTimesVector.push_back(TLSTEntry);
19533  IncTime = Utilities->IncrementAnsiTimeOneMinute(IncTime);
19534  }
19535  }
19536  }
19537 
19538  else if(AVE.SequenceType == FinishSequence) //other finish types - all located & all link to another service
19539  {
19540  //nothing is done here as the entry will be listed at this time under the new service reference
19541  }
19542  }
19543  }
19544  }
19545  SequenceLog += "5\n";
19546  //now sort in location order
19547  std::sort(LocServiceTimesVector.begin(), LocServiceTimesVector.end(), &LocServiceTimesLocationSort); //LocServiceTimesLocationSort is a function pointer
19548  //LocServiceTimesVector now complete & sorted in location order
19549 
19550 /*
19551 //start of debugging section
19552 //create LocServiceTimesVector output file for debugging purposes
19553  AnsiString LSTVTestFile = CurDir + "\\Formatted timetables\\LSTVTestFile; " + RailwayTitle + "; " + TimetableTitle + ".txt";
19554  std::ofstream LSTVFile(LSTVTestFile.c_str());
19555  for(TLocServiceTimesVector::iterator LSTVIt = LocServiceTimesVector.begin(); LSTVIt != LocServiceTimesVector.end(); LSTVIt++)
19556  {
19557  LSTVFile << LSTVIt->Location + '\n';
19558  LSTVFile << LSTVIt->ServiceAndRepeatNum + '\n';
19559  LSTVFile << "AtLocTime = " + LSTVIt->AtLocTime + '\n';
19560  LSTVFile << "ArrTime = " + LSTVIt->ArrTime + '\n';
19561  LSTVFile << "DepTime = " + LSTVIt->DepTime + '\n';
19562  if(LSTVIt->FrhMarker == "")
19563  {
19564  LSTVFile << "Not Frh\n";
19565  }
19566  else
19567  {
19568  LSTVFile << LSTVIt->FrhMarker + '\n';
19569  }
19570  LSTVFile << '\n';
19571  }
19572  LSTVFile.close();
19573  Utilities->CallLogPop();
19574  return(true);
19575 //end of debugging section
19576 */
19577  //declare pointers for use in printouts
19578  TLocServiceTimesVector::iterator Ptr1, Ptr2;
19579 
19580  //set up the output file
19581  AnsiString TTFileName3 = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
19582  TTFileName3 = CurDir + "\\Formatted timetables\\Conflict Analysis " + TTFileName3 + "; " + RailwayTitle + "; " + TimetableTitle + ".csv";
19583 
19584  std::ofstream TTFile3(TTFileName3.c_str());
19585 
19586  if(TTFile3 == 0)
19587  {
19588  ShowMessage("Conflict Analysis file failed to open - can't be created");
19589  Utilities->CallLogPop(2210);
19590  return(false);
19591  }
19592  if(LocServiceTimesVector.empty())
19593  {
19594  ShowMessage("No timetabled services found");
19595  TTFile3.close();
19596  DeleteFile(TTFileName3);
19597  Utilities->CallLogPop(2211);
19598  return(false);
19599  }
19600  TTFile3 << "Timetable analysis for timetable: '" + TimetableTitle + "' in conjunction with railway: '" + RailwayTitle + "'\n";
19601  TTFile3 << "See user manual or on-screen help section 5.12 for detailed information.\n\n\n";
19602  SequenceLog += "6\n";
19603 
19604 /*
19605 //print out TrainDataVectorCopy & TrainDataVector for debugging purposes, TrainDataVectorCopy first
19606 
19607 // Double crosslink (shuttle) table:
19608 //Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
19609 // Code ShuttleLink- EntryPtr ShuttleLink-
19610 // HeadCode EntryPtr
19611 
19612 //Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
19613 //Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
19614 //F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
19615 //Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
19616 //Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
19617 //Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
19618 //
19619 //Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
19620 
19621 //NOTE: from above for F-nshs & Sns-fsh in the TrainDataVectorCopy the OtherLinkedHeadCode will be correct as it is derived from LinkTrainEntryPtr which is correct
19622 //but for the original TrainDataVector the OtherLinkedHeadCode will be incorrect, hence have to use the NonRepeatingShuttleLinkHeadCode as that is the correct one
19623 //these were errors when first coded but work ok, just keep in mind when making any changes
19624 
19625 std::ofstream TDVCFile((CurDir + "\\Formatted timetables\\Conflict Analysis " + TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss") + "; " + RailwayTitle + "; " + TimetableTitle + " TrainDataVectorCopy.txt").c_str());
19626 AnsiString OHC = "", NRHC = "";
19627 AnsiString OLk = "", NRLk = "";
19628 TDVCFile << "Note that for the TrainDataVectorCopy OH and OLk should always be the same, as OH is derived from OHLk; and similarly for NR and NRLk\n\n";
19629 for(TTrainDataVector::iterator TDVCIt = TrainDataVectorCopy.begin(); TDVCIt != TrainDataVectorCopy.end(); TDVCIt++)
19630 {
19631  TDVCFile << TDVCIt->ServiceReference + '\n';
19632  TDVCFile << TDVCIt->Description + '\n';
19633  for(unsigned int x = 0; x < TDVCIt->ActionVector.size(); x++)
19634  {
19635  TActionVectorEntry AVE = TDVCIt->ActionVector.at(x);
19636  if(AVE.OtherHeadCode == "")
19637  {
19638  OHC = "OH 0";
19639  }
19640  else
19641  {
19642  OHC = "OH " + AVE.OtherHeadCode;
19643  }
19644  if(AVE.NonRepeatingShuttleLinkHeadCode == "")
19645  {
19646  NRHC = "NR 0";
19647  }
19648  else
19649  {
19650  NRHC = "NR " + AVE.NonRepeatingShuttleLinkHeadCode;
19651  }
19652  if(TDVCIt->ActionVector.at(x).LinkedTrainEntryPtr == 0)
19653  {
19654  OLk = "OLk 0";
19655  }
19656  else
19657  {
19658  OLk = "OLk " + TDVCIt->ActionVector.at(x).LinkedTrainEntryPtr->ServiceReference;
19659  }
19660  if(TDVCIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr == 0)
19661  {
19662  NRLk = "NRLk 0";
19663  }
19664  else
19665  {
19666  NRLk = "NRLk " + TDVCIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19667  }
19668 
19669  if(AVE.FormatType == TimeCmd) //cdt only
19670  {
19671  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << '\n';
19672  }
19673  if((AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle))
19674  {
19675  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19676  }
19677  else if((AVE.FormatType == FSHNewService) || (AVE.FormatType == SNSShuttle)) //these should have 2 linked services
19678  {
19679  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19680  }
19681  else if(AVE.FormatType == SNSNonRepeatFromShuttle)
19682  {
19683  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19684  }
19685  else if(AVE.FormatType == StartNew)
19686  {
19687  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt RearStartID " << Track->TrackElementAt(-1, AVE.RearStartOrRepeatMins).ElementID
19688  << " FrontStartID " << Track->TrackElementAt(-2, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19689  }
19690  else if(AVE.FormatType == SNTShuttle)
19691  {
19692  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt-sh RearStartID " << Track->TrackElementAt(-3, AVE.RearStartOrRepeatMins).ElementID
19693  << " FrontStartID " << Track->TrackElementAt(-4, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19694  }
19695  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
19696  {
19697  TDVCFile << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
19698  }
19699  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
19700  {
19701  TDVCFile << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
19702  }
19703  else if(AVE.FormatType == TimeTimeLoc)
19704  {
19705  TDVCFile << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
19706  }
19707  else if(AVE.FormatType == PassTime)
19708  {
19709  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
19710  }
19711  else if(AVE.FormatType == ExitRailway)
19712  {
19713  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Fer" << '\n';
19714  }
19715  else if(AVE.FormatType == FinRemHere)
19716  {
19717  TDVCFile << "Frh" << '\n';
19718  }
19719  }
19720  TDVCFile << '\n';
19721 }
19722 TDVCFile.close();
19723 
19724 //print out original TrainDataVector for comparison
19725 std::ofstream TDVFile((CurDir + "\\Formatted timetables\\Conflict Analysis " + TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss") + "; " + RailwayTitle + "; " + TimetableTitle + " TrainDataVector.txt").c_str());
19726 TDVFile << "Note that in the TrainDataVector, non-repeating shuttle link services F-nshs and Sns-fsh use the non-repeating headcode (NR) values for the corresponding "
19727  "shuttle headcodes when it should be the other headcode (OH), and the other headcode is unused. The link values are the right way round. Also OH & NR "
19728  "values ARE headcodes and not service references, but OLk and NRLk values are service references.\n\n";
19729 //F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
19730 //Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
19731 for(TTrainDataVector::iterator TDVIt = TrainDataVector.begin(); TDVIt != TrainDataVector.end(); TDVIt++)
19732 {
19733  TDVFile << TDVIt->ServiceReference + '\n';
19734  TDVFile << TDVIt->Description + '\n';
19735  for(unsigned int x = 0; x < TDVIt->ActionVector.size(); x++)
19736  {
19737  TActionVectorEntry AVE = TDVIt->ActionVector.at(x);
19738  if(AVE.OtherHeadCode == "")
19739  {
19740  OHC = "OH 0";
19741  }
19742  else
19743  {
19744  OHC = "OH " + AVE.OtherHeadCode;
19745  }
19746  if(AVE.NonRepeatingShuttleLinkHeadCode == "")
19747  {
19748  NRHC = "NR 0";
19749  }
19750  else
19751  {
19752  NRHC = "NR " + AVE.NonRepeatingShuttleLinkHeadCode;
19753  }
19754  if(TDVIt->ActionVector.at(x).LinkedTrainEntryPtr == 0)
19755  {
19756  OLk = "OLk 0";
19757  }
19758  else
19759  {
19760  OLk = "OLk " + TDVIt->ActionVector.at(x).LinkedTrainEntryPtr->ServiceReference;
19761  }
19762  if(TDVIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr == 0)
19763  {
19764  NRLk = "NRLk 0";
19765  }
19766  else
19767  {
19768  NRLk = "NRLk " + TDVIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19769  }
19770 
19771  if(AVE.FormatType == TimeCmd) //cdt only
19772  {
19773  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << '\n';
19774  }
19775  if((AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle))
19776  {
19777  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19778  }
19779  else if((AVE.FormatType == FSHNewService) || (AVE.FormatType == SNSShuttle)) //these should have 2 linked services
19780  {
19781  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19782  }
19783  else if(AVE.FormatType == SNSNonRepeatFromShuttle)
19784  {
19785  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19786  }
19787  else if(AVE.FormatType == StartNew)
19788  {
19789  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt RearStartID " << Track->TrackElementAt(-5, AVE.RearStartOrRepeatMins).ElementID
19790  << " FrontStartID " << Track->TrackElementAt(-6, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19791  }
19792  else if(AVE.FormatType == SNTShuttle)
19793  {
19794  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt-sh RearStartID " << Track->TrackElementAt(-7, AVE.RearStartOrRepeatMins).ElementID
19795  << " FrontStartID " << Track->TrackElementAt(-8, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19796  }
19797  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
19798  {
19799  TDVFile << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
19800  }
19801  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
19802  {
19803  TDVFile << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
19804  }
19805  else if(AVE.FormatType == TimeTimeLoc)
19806  {
19807  TDVFile << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
19808  }
19809  else if(AVE.FormatType == PassTime)
19810  {
19811  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
19812  }
19813  else if(AVE.FormatType == ExitRailway)
19814  {
19815  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Fer" << '\n';
19816  }
19817  else if(AVE.FormatType == FinRemHere)
19818  {
19819  TDVFile << "Frh" << '\n';
19820  }
19821  }
19822  TDVFile << '\n';
19823 }
19824 TDVFile.close();
19825 //end of debugging
19826 */
19827  //arrivals
19828  if(ArrChecked)
19829  {
19830  //sort in ArrTime order for each location
19831  Ptr1 = LocServiceTimesVector.begin();
19832  Ptr2 = Ptr1 + 1;
19833  while(Ptr2 != LocServiceTimesVector.end())
19834  {
19835  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
19836  {
19837  Ptr2++;
19838  if(Ptr2 == LocServiceTimesVector.end())
19839  {
19840  break;
19841  }
19842  }
19843  std::sort(Ptr1, Ptr2, &LocServiceTimesArrTimeSort);
19844  Ptr1 = Ptr2; //first entry with next name
19845  if(Ptr2 != LocServiceTimesVector.end())
19846  {
19847  Ptr2++;
19848  }
19849  }
19850 
19851  //routine for arrivals - number of trains arriving within the specified range with services listed at the end
19852 
19853  TTFile3 << "Arrival & pass analysis: an asterisk means that the number of same approach code arrivals and passes is equal to or greater than the number of platforms.\n";
19854  TTFile3 << "If the total number of arrivals and passes at the same time exceeds the number of platforms the 'Trains present at location analysis' will show an asterisk.\n\n";
19855  MinuteString = " minutes";
19856  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
19857  if(ArrRange == 1)
19858  {
19859  MinuteString = " minute";
19860  }
19861  TTFile3 << "Location,Number of,Number of,Services arriving within " << AnsiString(ArrRange) << MinuteString << " with their arrival times and approach codes\n";
19862  TTFile3 << ",Platforms,Trains\n\n";
19863 
19864  Ptr1 = LocServiceTimesVector.begin();
19865  Ptr2 = Ptr1 + 1;
19866  while(Ptr2 != LocServiceTimesVector.end())
19867  {
19868  PreviousService = "";
19869  NumTrainsAtLoc = 0;
19870  ServiceAndRepeatNumTotal = "";
19871  NumPlats = 0;
19872  NumPlatsAtThisLocCalculated = false;
19873  BasicTime = "";
19874  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
19875  {
19876  PreviousService = "";
19877  NumTrainsAtLoc = 0;
19878  ServiceAndRepeatNumTotal = "";
19879  NumPlats = 0;
19880  NumPlatsAtThisLocCalculated = false;
19881  BasicTime = "";
19882  Ptr1++;
19883  Ptr2++;
19884  if(Ptr2 == LocServiceTimesVector.end())
19885  {
19886  break;
19887  }
19888  }
19889  if(Ptr2 == LocServiceTimesVector.end())
19890  {
19891  break;
19892  }
19893  while(Ptr2->Location == Ptr1->Location)
19894  {
19895  PreviousService = "";
19896  NumTrainsAtLoc = 0;
19897  ServiceAndRepeatNumTotal = "";
19898  BasicTime = Ptr1->ArrTime; //used to compare later times - later pointer contents have same or later times as sorted in time order
19899  if((Ptr1->Location == "") && (Ptr2->Location == ""))
19900  {
19901  break;
19902  }
19903  while(!WithinTimeRange(0, BasicTime, Ptr2->ArrTime, ArrRange) || ((Ptr1->ArrTime == "") && (Ptr2->ArrTime == "")))
19904  {
19905  BasicTime = Ptr2->ArrTime; //used to compare later times or last can exceed first
19906  Ptr1++;
19907  Ptr2++;
19908  if(Ptr2 == LocServiceTimesVector.end())
19909  {
19910  break;
19911  }
19912  if(Ptr2->Location != Ptr1->Location)
19913  {
19914  break;
19915  }
19916  }
19917  if(Ptr2 == LocServiceTimesVector.end())
19918  {
19919  break;
19920  }
19921  if(Ptr2->Location != Ptr1->Location)
19922  {
19923  break;
19924  }
19925  while(WithinTimeRange(1, BasicTime, Ptr2->ArrTime, ArrRange))
19926  {
19927  if((Ptr1->ArrTime == "") && (Ptr2->ArrTime == ""))
19928  {
19929  break;
19930  }
19931  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
19932  {
19933  NumPlats = Track->NumberOfPlatforms(0, Ptr1->Location);
19934  NumPlatsAtThisLocCalculated = true;
19935  }
19936  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
19937  {
19938  if(ServiceAndRepeatNumTotal == "")
19939  {
19940  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum + "," + Ptr1->ArrTime;
19941  NumTrainsAtLoc = 1;
19942  }
19943  else
19944  {
19945  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum + "," + Ptr1->ArrTime;
19946  }
19947  }
19948  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ
19949  if(ServiceAndRepeatNumTotal == "")
19950  {
19951  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum + "," + Ptr2->ArrTime;
19952  NumTrainsAtLoc = 1;
19953  }
19954  else
19955  {
19956  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum + "," + Ptr2->ArrTime;
19957  }
19958  Ptr1 = Ptr2;
19959  Ptr2++;
19960  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (!WithinTimeRange(2, BasicTime, Ptr2->ArrTime, ArrRange)))
19961  {
19962  int MaxNumberOfSameDirections = 0;
19963  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTArrDep(1, ServiceAndRepeatNumTotal, NumTrainsAtLoc, Ptr1->Location, true, AnalysisError, MaxNumberOfSameDirections); //sort into alphabetical order and remove duplicates
19964  if(AnalysisError) //has to be Ptr1->Location as Ptr2 loc may have changed
19965  {
19966 // ShowMessage("Error in arrival analysis - file will be incomplete and/or corrupt. Please send railway and timetable files to railwayfeedback@gmail.com for investigation - thanks. Details: " + ServiceAndRepeatNumTotalOutput);
19967  TTFile3.close();
19968  throw Exception(ServiceAndRepeatNumTotalOutput.c_str());
19969 // Utilities->CallLogPop(2224);
19970 // return false;
19971  }
19972  AnsiString Asterisk = "";
19973  if(MaxNumberOfSameDirections >= NumPlats)
19974  {
19975  Asterisk = "* ";
19976  }
19977  //print out a single line for number of trains at loc with all service refs
19978  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << ServiceAndRepeatNumTotalOutput << '\n'; //no description as >1 service
19979  ArrivalsPrinted = true;
19980  ServiceAndRepeatNumTotal = "";
19981  }
19982  if(Ptr2 == LocServiceTimesVector.end())
19983  {
19984  break;
19985  }
19986  if(Ptr2->Location != Ptr1->Location)
19987  {
19988  break;
19989  }
19990  }
19991  if(Ptr2 == LocServiceTimesVector.end())
19992  {
19993  break;
19994  }
19995  }
19996  }
19997  if(!ArrivalsPrinted)
19998  {
19999  TTFile3 << "Nothing to report for arrivals";
20000  }
20001  TTFile3 << "\n\n";
20002  }
20003  //end of routine for arrivals
20004  SequenceLog += "7\n";
20005  //departures
20006  if(DepChecked)
20007  {
20008  //sort in DepTime order for each location
20009  Ptr1 = LocServiceTimesVector.begin();
20010  Ptr2 = Ptr1 + 1;
20011  while(Ptr2 != LocServiceTimesVector.end())
20012  {
20013  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
20014  {
20015  Ptr2++;
20016  if(Ptr2 == LocServiceTimesVector.end())
20017  {
20018  break;
20019  }
20020  }
20021  std::sort(Ptr1, Ptr2, &LocServiceTimesDepTimeSort);
20022  Ptr1 = Ptr2; //first entry with next name
20023  if(Ptr2 != LocServiceTimesVector.end())
20024  {
20025  Ptr2++;
20026  }
20027  }
20028 
20029  //routine for departures - number of trains departing within the specified range with services listed at the end
20030  TTFile3 << "Departure & pass analysis: an asterisk means that the number of same exit code departures and passes is equal to or greater than the number of platforms.\n";
20031  TTFile3 << "If the total number of departures and passes at the same time exceeds the number of platforms the 'Trains present at location analysis' will show an asterisk.\n\n";
20032  MinuteString = " minutes";
20033  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
20034  if(DepRange == 1)
20035  {
20036  MinuteString = " minute";
20037  }
20038  TTFile3 << "Location,Number of,Number of,Services departing within " << AnsiString(DepRange) << MinuteString << " with their departure times and exit codes\n";
20039  TTFile3 << ",Platforms,Trains\n\n";
20040 
20041  Ptr1 = LocServiceTimesVector.begin();
20042  Ptr2 = Ptr1 + 1;
20043  while(Ptr2 != LocServiceTimesVector.end())
20044  {
20045  PreviousService = "";
20046  NumTrainsAtLoc = 0;
20047  ServiceAndRepeatNumTotal = "";
20048  NumPlats = 0;
20049  NumPlatsAtThisLocCalculated = false;
20050  BasicTime = "";
20051  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
20052  {
20053  PreviousService = "";
20054  NumTrainsAtLoc = 0;
20055  ServiceAndRepeatNumTotal = "";
20056  NumPlats = 0;
20057  NumPlatsAtThisLocCalculated = false;
20058  BasicTime = "";
20059  Ptr1++;
20060  Ptr2++;
20061  if(Ptr2 == LocServiceTimesVector.end())
20062  {
20063  break;
20064  }
20065  }
20066  if(Ptr2 == LocServiceTimesVector.end())
20067  {
20068  break;
20069  }
20070  while(Ptr2->Location == Ptr1->Location)
20071  {
20072  PreviousService = "";
20073  NumTrainsAtLoc = 0;
20074  ServiceAndRepeatNumTotal = "";
20075  BasicTime = Ptr1->DepTime; //used to compare later times - later pointer contents have same or later times as sorted in time order
20076  if((Ptr1->Location == "") && (Ptr2->Location == ""))
20077  {
20078  break;
20079  }
20080  while(!WithinTimeRange(3, BasicTime, Ptr2->DepTime, DepRange) || ((Ptr1->DepTime == "") && (Ptr2->DepTime == "")))
20081  {
20082  BasicTime = Ptr2->DepTime; //used to compare later times or last can exceed first
20083  Ptr1++;
20084  Ptr2++;
20085  if(Ptr2 == LocServiceTimesVector.end())
20086  {
20087  break;
20088  }
20089  if(Ptr2->Location != Ptr1->Location)
20090  {
20091  break;
20092  }
20093  }
20094  if(Ptr2 == LocServiceTimesVector.end())
20095  {
20096  break;
20097  }
20098  if(Ptr2->Location != Ptr1->Location)
20099  {
20100  break;
20101  }
20102  while(WithinTimeRange(4, BasicTime, Ptr2->DepTime, DepRange))
20103  {
20104  if((Ptr1->DepTime == "") && (Ptr2->DepTime == ""))
20105  {
20106  break;
20107  }
20108  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
20109  {
20110  NumPlats = Track->NumberOfPlatforms(1, Ptr1->Location);
20111  NumPlatsAtThisLocCalculated = true;
20112  }
20113  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
20114  {
20115  if(ServiceAndRepeatNumTotal == "")
20116  {
20117  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum + "," + Ptr1->DepTime;
20118  NumTrainsAtLoc = 1;
20119  }
20120  else
20121  {
20122  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum + "," + Ptr1->DepTime;
20123  }
20124  }
20125  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ
20126  if(ServiceAndRepeatNumTotal == "")
20127  {
20128  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum + "," + Ptr2->DepTime;
20129  NumTrainsAtLoc = 1;
20130  }
20131  else
20132  {
20133  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum + "," + Ptr2->DepTime;
20134  }
20135  Ptr1 = Ptr2;
20136  Ptr2++;
20137  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (!WithinTimeRange(5, BasicTime, Ptr2->DepTime, DepRange)))
20138  {
20139  int MaxNumberOfSameDirections = 0;
20140  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTArrDep(3, ServiceAndRepeatNumTotal, NumTrainsAtLoc, Ptr1->Location, false, AnalysisError, MaxNumberOfSameDirections); //sort into alphabetical order and remove duplicates
20141  if(AnalysisError) //has to be Ptr1->Location as Ptr2 loc may have changed
20142  {
20143 // ShowMessage("Error in departure analysis - file will be incomplete and/or corrupt. Please send railway and timetable files to railwayfeedback@gmail.com for investigation - thanks. Details: " + ServiceAndRepeatNumTotalOutput);
20144  TTFile3.close();
20145  throw Exception(ServiceAndRepeatNumTotalOutput.c_str());
20146 // Utilities->CallLogPop(2225);
20147 // return false;
20148  }
20149  AnsiString Asterisk = "";
20150  if(MaxNumberOfSameDirections >= NumPlats)
20151  {
20152  Asterisk = "* ";
20153  }
20154  //print out a single line for number of trains at loc with all service refs
20155  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << ServiceAndRepeatNumTotalOutput << '\n'; //no description as >1 service
20156  DeparturesPrinted = true;
20157  ServiceAndRepeatNumTotal = "";
20158  }
20159  if(Ptr2 == LocServiceTimesVector.end())
20160  {
20161  break;
20162  }
20163  if(Ptr2->Location != Ptr1->Location)
20164  {
20165  break;
20166  }
20167  }
20168  if(Ptr2 == LocServiceTimesVector.end())
20169  {
20170  break;
20171  }
20172  }
20173  }
20174  if(!DeparturesPrinted)
20175  {
20176  TTFile3 << "Nothing to report for departures";
20177  }
20178  TTFile3 << "\n\n";
20179  }
20180  //end of routine for departures
20181  SequenceLog += "8\n";
20182 
20183  //list trains at locations at same time
20184 
20185  if(AtLocChecked)
20186  {
20187  //sort in AtLocTime order for each location
20188  Ptr1 = LocServiceTimesVector.begin();
20189  Ptr2 = Ptr1 + 1;
20190  while(Ptr2 != LocServiceTimesVector.end())
20191  {
20192  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
20193  {
20194  Ptr2++;
20195  if(Ptr2 == LocServiceTimesVector.end())
20196  {
20197  break;
20198  }
20199  }
20200  std::sort(Ptr1, Ptr2, &LocServiceTimesAtLocTimeSort);
20201  Ptr1 = Ptr2; //first entry with next name
20202  if(Ptr2 != LocServiceTimesVector.end())
20203  {
20204  Ptr2++;
20205  }
20206  }
20207 
20208  //print out simultaneous AtLocs (don't need range of times for AtLocs)
20209  TTFile3 << "Trains present at location analysis: an asterisk means that the number of trains at the location is greater than the number of platforms.\n\n";
20210  TTFile3 << "Location,Number of,Number of,Time,Services at the location at that time\n";
20211  TTFile3 << ",Platforms,Trains,\n\n";
20212  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
20213  Ptr1 = LocServiceTimesVector.begin();
20214  Ptr2 = Ptr1 + 1;
20215  while(Ptr2 != LocServiceTimesVector.end())
20216  {
20217  PreviousService = "";
20218  ServiceAndRepeatNumTotal = "";
20219  NumTrainsAtLoc = 0;
20220  NumPlats = 0;
20221  NumPlatsAtThisLocCalculated = false;
20222  FrhCount = 0;
20223 
20224  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
20225  {
20226  PreviousService = "";
20227  ServiceAndRepeatNumTotal = "";
20228  NumTrainsAtLoc = 0;
20229  NumPlats = 0;
20230  NumPlatsAtThisLocCalculated = false;
20231  FrhCount = 0;
20232  Ptr1++;
20233  Ptr2++;
20234  if(Ptr2 == LocServiceTimesVector.end())
20235  {
20236  break;
20237  }
20238  }
20239  if(Ptr2 == LocServiceTimesVector.end())
20240  {
20241  break;
20242  }
20243  while(Ptr2->Location == Ptr1->Location)
20244  {
20245  if(Ptr1->FrhMarker == "Frh") //this test is made here and each time Ptr1 increases with Ptr1 & 2 at same loc so as to catch them all
20246  {
20247  FrhCount++;
20248  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
20249  }
20250  PreviousService = "";
20251  NumTrainsAtLoc = 0;
20252  ServiceAndRepeatNumTotal = "";
20253  if((Ptr1->Location == "") && (Ptr2->Location == ""))
20254  {
20255  break;
20256  }
20257  while((Ptr2->AtLocTime != Ptr1->AtLocTime) || ((Ptr1->AtLocTime == "") && (Ptr2->AtLocTime == "")))
20258  {
20259  Ptr1++;
20260  if(Ptr1->FrhMarker == "Frh")
20261  {
20262  FrhCount++;
20263  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
20264  }
20265  Ptr2++;
20266  if(Ptr2 == LocServiceTimesVector.end())
20267  {
20268  break;
20269  }
20270  if(Ptr2->Location != Ptr1->Location)
20271  {
20272  break;
20273  }
20274  }
20275  if(Ptr2 == LocServiceTimesVector.end())
20276  {
20277  break;
20278  }
20279  if(Ptr2->Location != Ptr1->Location)
20280  {
20281  break;
20282  }
20283  while(Ptr2->AtLocTime == Ptr1->AtLocTime)
20284  {
20285  if((Ptr1->AtLocTime == "") && (Ptr2->AtLocTime == ""))
20286  {
20287  break;
20288  }
20289  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
20290  {
20291  NumPlats = Track->NumberOfPlatforms(2, Ptr1->Location);
20292  NumPlatsAtThisLocCalculated = true;
20293  }
20294  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
20295  {
20296  if(ServiceAndRepeatNumTotal == "")
20297  {
20298  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum;
20299  NumTrainsAtLoc = 1;
20300  }
20301  else
20302  {
20303  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum;
20304  }
20305  }
20306  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ, has to be Ptr2 to compare Ptr1 at next round when incremented
20307  if(ServiceAndRepeatNumTotal == "")
20308  {
20309  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum;
20310  NumTrainsAtLoc = 1;
20311  }
20312  else
20313  {
20314  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum;
20315  }
20316  Ptr1 = Ptr2;
20317  if(Ptr1->FrhMarker == "Frh")
20318  {
20319  FrhCount++;
20320  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
20321  }
20322  Ptr2++;
20323  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (Ptr2->AtLocTime != Ptr1->AtLocTime))
20324  {
20325 //old text //only print out if no remainers (1st condition), change in remainers (2nd condition) or change in ServiceAndRepeatNumTotalOutput, and >1 train (later condition)
20326 //new text //don't print out if all remainers or if only 1 train at loc
20327  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTAtLoc(1, ServiceAndRepeatNumTotal, NumTrainsAtLoc); //sort into alphabetical order, remove duplicates, and calculate new value for NumTrainsAtLoc
20328 //old condits if((FrhCount == 0) || (FrhCount != LastFrhCount) || (PreviousServiceAndRepeatNumTotalOutput != ServiceAndRepeatNumTotalOutput))//don't print if same output
20329 /*new condits*/ if((NumTrainsAtLoc > 1) && ((FrhCount < NumTrainsAtLoc) || (FrhCount != LastFrhCount)))
20330  {
20331  AnsiString Asterisk = "";
20332  if(NumTrainsAtLoc > NumPlats)
20333  {
20334  Asterisk = "* ";
20335  }
20336  //print out a single line for number of trains at loc with all service refs
20337  if(FrhCount == 0)
20338  {
20339  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << "," << ServiceAndRepeatNumTotalOutput << '\n';
20340  }
20341  else if(FrhCount == 1)
20342  {
20343  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << " (1 remains here)," << ServiceAndRepeatNumTotalOutput << '\n';
20344  }
20345  else
20346  {
20347  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << " (" << FrhCount << " remain here)," << ServiceAndRepeatNumTotalOutput << '\n';
20348  }
20349  LastFrhCount = FrhCount;
20350  PreviousServiceAndRepeatNumTotalOutput = ServiceAndRepeatNumTotalOutput;
20351  AtLocsPrinted = true;
20352  ServiceAndRepeatNumTotal = "";
20353  }
20354  }
20355  if(Ptr2 == LocServiceTimesVector.end())
20356  {
20357  break;
20358  }
20359  if(Ptr2->Location != Ptr1->Location)
20360  {
20361  break;
20362  }
20363  }
20364  if(Ptr2 == LocServiceTimesVector.end())
20365  {
20366  break;
20367  }
20368  }
20369  }
20370  if(!AtLocsPrinted)
20371  {
20372  TTFile3 << "Nothing to report for trains at locations";
20373  }
20374  TTFile3 << "\n\n";
20375  //end of simultaneous AtLocs
20376  }
20377  SequenceLog += "9\n";
20378 
20379 /*
20380 //start of debugging section
20381  //print out the full vector here for testing purposes
20382  TTFile3 << "Full LocServiceTimesVector\n\n";
20383  TTFile3 << "Location,AtLocTime,ArrTime,DepTime,ServiceAndRepeatNum,Description\n\n";
20384 
20385  for(TLocServiceTimesVector::iterator Ptr = LocServiceTimesVector.begin(); Ptr != LocServiceTimesVector.end(); Ptr++)
20386  {
20387  TTFile3 << Ptr->Location << "," << Ptr->AtLocTime << "," << Ptr->ArrTime << "," << Ptr->DepTime << "," << Ptr->ServiceAndRepeatNum << "," << Ptr->FrhMarker << '\n';
20388  }
20389 
20390  TTFile3 << "\n\n\n";
20391 //end of debugging
20392 */
20393 
20394 /*cdt analysis - added at v2.10.0
20395 2 pass system: 1st extract as a single service all Snt (or Snt-sh) starts, with Fns/Sns links combined (and F-nshs/Sns-sh) (though add a new
20396 changeover code [chr XXXX - 'change ref + new reference] until come to Fjo, Frh, Frh-sh, Fer (ignore exit loc as can't stop there), ignore jbos &
20397 repeats, but with fsp & rsp store all the foregoing service entries along with the split reference & add that to the relevant Sfs entry as a new
20398 service. For shuttles with feeder start with feeder & progress into shuttle, ending when finish & remain here or progressing into the finishing
20399 service.
20400 
20401 Use The TrainDataVectorCopy as that has all unique service refs.
20402 
20403 2nd run the cdt checker similar to that in SecondPassActions, but where a same name found either side of a changeover code quote both refs. Add a
20404 similar unexpected cdt check where if have different locs either side of a cdt then may be inappropriate.
20405 
20406 First create a new TrainDataVector from earlier copy as above with single services
20407 */
20408  if(DirChecked)
20409  {
20410  //direction analysis added at v2.10.0
20411  TTrainDataEntry SingleServiceEntry, PartServiceEntry, NewPartServiceEntry, TempEntry;
20412  TTrainDataVector SingleServiceVector, PartServiceVector;
20413 
20414  //find new train services (Snt or Snt-sh) & remember that entries can be in any order
20415  //NB: ALWAYS use OtherHeadCode (which is now a service reference) for any follow-on service
20416  TTFile3 << "Train direction analysis - consisting of train facing directions on creation and possible missing or questionable changes of direction:\n\n";
20417  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
20418  {
20420  if(TDE.ActionVector.at(TDE.ActionVector.size() - 1).FormatType == Repeat)
20421  {
20422  TDE.ActionVector.erase(TDE.ActionVector.end() - 1); //strip repeat entry if present
20423  }
20424  const TActionVector &AV = TDE.ActionVector;
20425  if((AV.at(0).Command == "Snt") || (AV.at(0).Command == "Snt-sh"))
20426  {
20427  SingleServiceEntry = TDE;
20428  TActionVector &SSAV = SingleServiceEntry.ActionVector;
20429  for(unsigned int y = 0; y < SSAV.size(); y++)
20430  {
20431  if((SSAV.at(y).Command == "Fjo") || (SSAV.at(y).Command == "Frh") || (SSAV.at(y).Command == "Fer") || (SSAV.at(y).Command == "Frh-sh"))
20432  {
20433  SingleServiceVector.push_back(SingleServiceEntry); //push the complete entry
20434  break; //finished with this one
20435  }
20436  else if((SSAV.at(y).Command == "fsp") || (SSAV.at(y).Command == "rsp"))
20437  {
20438  PartServiceEntry = TDE; //start with complete entry
20439  PartServiceEntry.ActionVector.clear(); //clear AV
20440  for(unsigned int z = 0; z <= y; z++)
20441  {
20442  PartServiceEntry.ActionVector.push_back(TDE.ActionVector.at(z)); //add back all AVs up to & inc fsp/rsp
20443  if(z == y)
20444  {
20445  PartServiceEntry.ActionVector.at(z).Command = "chr-sp"; //change split command to chr
20446  PartServiceEntry.ActionVector.at(z).OtherHeadCode = PartServiceEntry.ActionVector.at(z).LinkedTrainEntryPtr->ServiceReference;
20447  }
20448  }
20449  PartServiceVector.push_back(PartServiceEntry);
20450  if(SSAV.at(y).Command == "fsp")
20451  {
20452  SSAV.at(y).Command = "Front split - original service continues below";
20453  SSAV.at(y).OtherHeadCode = "";
20454  }
20455  if(SSAV.at(y).Command == "rsp")
20456  {
20457  SSAV.at(y).Command = "Rear split - original service continues below";
20458  SSAV.at(y).OtherHeadCode = "";
20459  }
20460  //don't break & continue here because the original train carries on
20461  }
20462  else if(SSAV.at(y).Command == "Fns")
20463  {
20464  SSAV.at(y).Command = "chr-Fns";
20465  SSAV.at(y).OtherHeadCode = SSAV.at(y).LinkedTrainEntryPtr->ServiceReference;
20466  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
20467  break; //from y loop
20468  }
20469  else if(SSAV.at(y).Command == "Fns-sh")
20470  {
20471  SSAV.at(y).Command = "chr-Fns-sh";
20472  SSAV.at(y).OtherHeadCode = SSAV.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
20473  SSAV.at(y).NonRepeatingShuttleLinkHeadCode = "";
20474  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
20475  break; //from y loop
20476  }
20477  else if(SSAV.at(y).Command == "F-nshs")
20478  {
20479  SSAV.at(y).Command = "chr-F-nshs"; //NonRepeatingShuttleLinkHeadCode is the shuttle headcode
20480  SSAV.at(y).OtherHeadCode = SSAV.at(y).LinkedTrainEntryPtr->ServiceReference;
20481  SSAV.at(y).NonRepeatingShuttleLinkHeadCode = "";
20482  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
20483  break; //from y loop
20484  }
20485  }
20486  }
20487  }
20488  SequenceLog += "10\n";
20489  //now have all complete entries in SingleServiceVector and all part services in PartServiceVector but without any follow-ons
20490 
20491  //Now add Sns & Sns-sh services to PartServiceVector entries
20492  AnsiString NextRef;
20493  while(!PartServiceVector.empty())
20494  {
20495  PartServiceEntry = PartServiceVector.at(0); //deal with front entry and add new entries at the back
20496  for(unsigned int y = 0; y < PartServiceEntry.ActionVector.size(); y++)
20497  {
20498  if(PartServiceEntry.ActionVector.at(y).Command.SubString(1,3) == "chr")
20499  {
20500  NextRef = PartServiceEntry.ActionVector.at(y).OtherHeadCode;
20501  }
20502  }
20503  //find it in TrainDataVectorCopy
20504  bool FinishType = true, FoundFlag = false;
20505  while(FinishType)
20506  {
20507  TempEntry = GetServiceFromVector(0, NextRef, TrainDataVectorCopy, FinishType, FoundFlag); //FinishType is a bool where false = Final (Fjo, Frh, Fer, or
20508  //Frh-sh); true = MoreToCome (Fns, Fns-sh, F-nshs)
20509  if(FoundFlag)
20510  {
20511  for(unsigned int y = 1; y < TempEntry.ActionVector.size(); y++) //starts at 1 as that is the entry after the start entry
20512  {
20513  if((TempEntry.ActionVector.at(y).Command == "") && (TempEntry.ActionVector.at(y).FormatType != Repeat))
20514  {
20515  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20516  }
20517  else if((TempEntry.ActionVector.at(y).Command[1] != 'F') && (TempEntry.ActionVector.at(y).Command != "fsp") && (TempEntry.ActionVector.at(y).Command != "rsp") && (TempEntry.ActionVector.at(y).FormatType != Repeat))
20518  {
20519  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20520  }
20521  else
20522  {
20523  if((TempEntry.ActionVector.at(y).Command == "Fjo") || (TempEntry.ActionVector.at(y).Command == "Frh") || (TempEntry.ActionVector.at(y).Command == "Fer") || (TempEntry.ActionVector.at(y).Command == "Frh-sh"))
20524  {
20525  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20526  SingleServiceVector.push_back(PartServiceVector.at(0)); //push the complete entry
20527  PartServiceVector.erase(PartServiceVector.begin());
20528  break; //from y loop
20529  }
20530  else if((TempEntry.ActionVector.at(y).Command == "fsp") || (TempEntry.ActionVector.at(y).Command == "rsp"))
20531  {
20532  NewPartServiceEntry = PartServiceVector.at(0); //covers everything up to but excluding the split
20533  NewPartServiceEntry.ActionVector.push_back(TempEntry.ActionVector.at(y)); //now includes the split
20534  NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).Command = "chr-sp"; //change split command to chr
20535  NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).OtherHeadCode = NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).LinkedTrainEntryPtr->ServiceReference;
20536  PartServiceVector.push_back(NewPartServiceEntry); //new entry for the split service
20537  if(TempEntry.ActionVector.at(y).Command == "fsp")
20538  {
20539  TempEntry.ActionVector.at(y).Command = "Front split - original service continues below";
20540  TempEntry.ActionVector.at(y).OtherHeadCode = "";
20541  }
20542  if(TempEntry.ActionVector.at(y).Command == "rsp")
20543  {
20544  TempEntry.ActionVector.at(y).Command = "Rear split - original service continues below";
20545  TempEntry.ActionVector.at(y).OtherHeadCode = "";
20546  }
20547  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20548  }
20549  else if(TempEntry.ActionVector.at(y).Command == "Fns")
20550  {
20551  TempEntry.ActionVector.at(y).Command = "chr-Fns";
20552  NextRef = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
20553  TempEntry.ActionVector.at(y).OtherHeadCode = NextRef;
20554  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
20555  break; //from y loop
20556  }
20557  else if(TempEntry.ActionVector.at(y).Command == "Fns-sh")
20558  {
20559  TempEntry.ActionVector.at(y).Command = "chr-Fns-sh";
20560  TempEntry.ActionVector.at(y).OtherHeadCode = TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
20561  TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkHeadCode = "";
20562  NextRef = TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
20563  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
20564  break; //from y loop
20565  }
20566  else if(TempEntry.ActionVector.at(y).Command == "F-nshs")
20567  {
20568  TempEntry.ActionVector.at(y).Command = "chr-F-nshs"; //NonRepeatingShuttleLinkHeadCode is the shuttle headcode
20569  TempEntry.ActionVector.at(y).OtherHeadCode = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
20570  TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkHeadCode = "";
20571  NextRef = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
20572  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
20573  break; //from y loop
20574  }
20575  }
20576  }
20577  }
20578  else
20579  {
20580  SequenceLog += + "11\n";
20581  throw Exception("Unable to find service reference " + NextRef + " Last ref checked = " + TempEntry.ServiceReference);
20582  }
20583  }
20584  }
20585  if(!PartServiceVector.empty())
20586  {
20587  SequenceLog += "12\n";
20588  throw Exception("PartServiceVector should be empty here - size = " + AnsiString(PartServiceVector.size()));
20589  }
20590  SequenceLog += "13\n";
20591  /*
20592  form:-
20593  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
20594  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
20595  then multiple entries, separated by commas, of the form:-
20596 
20597  HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
20598  HH:MM;Snt-sh;RearStartIdent FrontStartIdent;Fsh HeadCode }SNTShuttle }
20599  HH:MM;Sns-sh;Fxx-sh HeadCode;F-nshs HeadCode (non-repeating)}SNSShuttle }
20600 
20601  HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode } Train action entries
20602  HH:MM;F-nshs;NonRepeatingShuttleLinkHeadCode }FNSNonRepeatToShuttle }
20603  HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle }
20604 
20605  HH:MM;Command (cdt) }TimeCmd }
20606  HH:MM;Command;new description (dsc) }TimeCmdDescription }
20607  HH:MM;Command;new maximum speed (cms) }TimeCmdMaxSpeed }
20608  HH:MM;Location (arr & dep) }TimeLoc }
20609  HH:MM;HH:MM;Location }TimeTimeLoc }
20610  HH:MM;pas;Location }PassTime }
20611  HH:MM;Fns-sh;Snx-sh HeadCode;Sns-fsh HeadCode (non-rep) }FSHNewService }
20612  HH:MM;Fer;set of allowable IDs }ExitRailway }
20613  Command (Frh only) }FinRemHere }
20614 
20615  R;mm;dd;nn. Repeat Repeat entry
20616 
20617  Formats:
20618 
20619  Command only: Frh
20620  Time;Command: cdt
20621  Time;Command;new description: dsc
20622  Time;Command;new max speed: cms
20623  Time;Command;Headcode: Sfs Sns jbo fsp rsp Fns Fjo Frh-sh F-nshs Sns-fsh
20624  Time;Command;2 Element IDs: Snt
20625  Time;Comand;n Element IDs: Fer
20626  Time;Command;rep Headcode;nonrep Headcode: Sns-sh Fns-sh
20627  Time;Command;2 Element IDs;Headcode Snt-sh
20628  Time;Command;Location pas
20629  Time;Location Arr Dep
20630  Time;Time;Location Arr & dep together
20631  */
20632 
20633 /*
20634 Perform the starting direction check (Snt & Snt-sh entries). Starts with the train's front element,
20635 checking forwards until it comes to a continuation (no report), a location name that is not null and
20636 different to the train's front element name (whether null or not) (no report), a leading point
20637 (no report) or buffers (report).
20638 */
20639  bool BufferFacingUnReportedFlag = true;
20640  bool TrainFacingBuffersReported = false; //new flag added at v2.19.0 for 'no facing buffers' message instead of BufferFacingUnReportedFlag
20641  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20642  {
20643  TTrackElement ThisElement, NextElement;
20644  TTrainDataEntry TDE = SingleServiceVector.at(x);
20645  if(TDE.ActionVector.at(TDE.ActionVector.size() - 1).FormatType == Repeat)
20646  {
20647  SequenceLog += "13a\n";
20648  throw Exception("Repeat entry present in SingleServiceVector at position " + AnsiString(x));
20649  }
20650  const TActionVector &AV = TDE.ActionVector;
20651  if((AV.at(0).Command == "Snt") || (AV.at(0).Command == "Snt-sh"))
20652  {
20653  bool BufferFlag = false;
20654  int FrontTVPos = AV.at(0).FrontStartOrRepeatDigits;
20655  int RearTVPos = AV.at(0).RearStartOrRepeatMins;
20656  AnsiString FrontLocName = AV.at(0).LocationName;
20657  int NextEntryPos, NextExitPos;
20658  ThisElement = Track->TrackElementAt(1395, FrontTVPos);
20659  int ThisExitPos;
20660  if(ThisElement.Conn[0] == RearTVPos)
20661  {
20662  ThisExitPos = 1;
20663  }
20664  else if(ThisElement.Conn[1] == RearTVPos)
20665  {
20666  ThisExitPos = 0;
20667  }
20668  else if(ThisElement.Conn[2] == RearTVPos)
20669  {
20670  ThisExitPos = 3;
20671  }
20672  else if(ThisElement.Conn[3] == RearTVPos)
20673  {
20674  ThisExitPos = 2;
20675  }
20676  if((ThisElement.TrackType == Buffers) && (ThisExitPos == 0))//pos 0 is the buffer
20677  {
20678  BufferFlag = true;
20679  }
20680  else //continue tracking forwards
20681  {
20682  while(true)
20683  {
20684  if(ThisElement.Conn[ThisExitPos] == -1)
20685  {
20686  SequenceLog = "13b\n";
20687  throw Exception("ThisElement connects to -1 for " + TDE.ServiceReference);
20688  }
20689  NextElement = Track->TrackElementAt(1396, ThisElement.Conn[ThisExitPos]);
20690  NextEntryPos = ThisElement.ConnLinkPos[ThisExitPos];
20691  if((NextElement.TrackType == Points) && ((NextEntryPos == 0) || (NextEntryPos == 2))) //leading points
20692  {
20693  BufferFlag = false; //should already be false
20694  break;
20695  }
20696  else if(NextElement.TrackType == Continuation)
20697  {
20698  BufferFlag = false;
20699  break;
20700  }
20701  else if((NextElement.ActiveTrackElementName != "") && (NextElement.ActiveTrackElementName != FrontLocName))
20702  {
20703  BufferFlag = false;
20704  break;
20705  }
20706  else if(NextElement.TrackType == Buffers)
20707  {
20708  BufferFlag = true;
20709  break;
20710  }
20711  else if((NextElement.TrackType == Points) && ((NextEntryPos == 1) || (NextEntryPos == 3))) //trailing points
20712  {
20713  ThisElement = NextElement;
20714  ThisExitPos = 0;
20715  continue;
20716  }
20717  else
20718  {
20719  if(NextEntryPos == 0)
20720  {
20721  NextExitPos = 1;
20722  }
20723  else if(NextEntryPos == 1)
20724  {
20725  NextExitPos = 0;
20726  }
20727  else if(NextEntryPos == 2)
20728  {
20729  NextExitPos = 3;
20730  }
20731  else if(NextEntryPos == 3)
20732  {
20733  NextExitPos = 2;
20734  }
20735  }
20736  ThisElement = NextElement;
20737  ThisExitPos = NextExitPos;
20738  }
20739  }
20740  if(BufferFlag)
20741  {
20742  if(BufferFacingUnReportedFlag)
20743  {
20744  TTFile3 << "Train facing direction on creation analysis:-\n\n";
20745  BufferFacingUnReportedFlag = false;
20746  }
20747  if(AV.at(1).Command != "cdt") //added at v2.19.0
20748  {
20749  TTFile3 << "Service " + TDE.ServiceReference + " facing buffers on creation with no immediate change of direction\n";
20750  TrainFacingBuffersReported = true; //added at v2.19.0
20751  }
20752  }
20753  }
20754  }
20755  if(!TrainFacingBuffersReported) //added at v2.19.0
20756  {
20757  TTFile3 << "Nothing to report for train facing directions\n\n";
20758  }
20759  else
20760  {
20761  TTFile3 << '\n';
20762  }
20763  SequenceLog += "13c\n";
20764 
20765  //Perform the missing cdt check. Check every entry similar to the check in SecondPassActions and if find any print out the full sequence and service entries
20766  AnsiString LocationNameToBeChecked = "";
20767  bool MissingcdtUnreportedFlag = true;
20768  TNumList MarkerList;
20769  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20770  {
20771  const TTrainDataEntry &TDEntry = SingleServiceVector.at(x);
20772  unsigned int y = 0;
20773  int FirstInstance = 9999, SecondInstance = 9999; //9999 ensures won't be marked if not changed
20774  bool FullBreak = false;
20775  MarkerList.clear();
20776  // first discard unlocated Snt entries as they don't have location name set
20777  while((y < TDEntry.ActionVector.size()) && !FullBreak)
20778  // need to check each location name separately in turn, skipped for SignallerControl entries
20779  {
20780  if(/*(TDEntry.ActionVector.at(y).Command == "Fer") || */(TDEntry.ActionVector.at(y).FormatType == Repeat) ||
20781  (TDEntry.ActionVector.at(y).Command == "Fjo") || (TDEntry.ActionVector.at(y).Command == "Frh") ||
20782  (TDEntry.ActionVector.at(y).Command == "Frh-sh"))
20783  {
20784  break; // out of the 'while' loop since have reached the end
20785  }
20786  LocationNameToBeChecked = ""; //this section where continuation name assigned for unlocated Snts added at v2.18.0
20787  if((TDEntry.ActionVector.at(y).Command == "Snt") && (TDEntry.ActionVector.at(y).LocationType == EnRoute)) //unlocated Snt - check if the continuation has a name
20788  {
20789  int EntryPos = TDEntry.ActionVector.at(0).RearStartOrRepeatMins; //this is a track vector position
20790  LocationNameToBeChecked = Track->TrackElementAt(1678, EntryPos).ActiveTrackElementName;
20791  }
20792  if(LocationNameToBeChecked == "")
20793  {
20794  if(y == 0) //unlocated and un-named Snt, so skip this value of y
20795  {
20796  y++;
20797  continue;
20798  }
20799  LocationNameToBeChecked = TDEntry.ActionVector.at(y).LocationName; //the only un-named values for ActionVectorEntry::LocationName
20800  } //are for unlocated Snts and Fers
20801  FirstInstance = y;
20802  for(unsigned int z = y; z < TDEntry.ActionVector.size(); z++)
20803  {
20804  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
20805  if(/*(AVEntry.Command == "Fer") || */(AVEntry.FormatType == Repeat) ||
20806  (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh") ||
20807  (AVEntry.Command == "Frh-sh"))
20808  {
20809  break; // out of the 'z' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
20810  }
20811  if(AVEntry.Command == "cdt")
20812  {
20813  break; // out of the 'z' loop since the check is only valid up to a change of direction
20814  }
20815  if(AVEntry.LocationName == LocationNameToBeChecked)
20816  {
20817  continue; // keep going while name same
20818  }
20819  if(AVEntry.LocationName != LocationNameToBeChecked)
20820  // if name different check forwards to see if repeats
20821  {
20822  for(unsigned int a = z; a < TDEntry.ActionVector.size(); a++)
20823  {
20824  AnsiString LocationName;
20825  if(TDEntry.ActionVector.at(a).Command == "cdt")
20826  {
20827  break; // out of the 'a' & 'z' loops since the check is only valid up to a change of direction
20828  }
20829  if(TDEntry.ActionVector.at(a).Command == "Fer") //this section where continuation name assigned for Fers added at v2.18.0
20830  {
20831  int ExitLoc = TDEntry.ActionVector.at(a).ExitList.front();
20832  LocationName = Track->TrackElementAt(1679, ExitLoc).ActiveTrackElementName;
20833  }
20834  else
20835  {
20836  LocationName = TDEntry.ActionVector.at(a).LocationName;
20837  }
20838  if(LocationName == LocationNameToBeChecked)
20839  {
20840  SecondInstance = a;
20841  AnsiString Sequence = TDEntry.ServiceReference;
20842  for(unsigned int b = 0; b < TDEntry.ActionVector.size(); b++)
20843  {
20844  if(TDEntry.ActionVector.at(b).Command.SubString(1,3) == "chr")
20845  {
20846  Sequence = Sequence + AnsiString(" -> ") + TDEntry.ActionVector.at(b).OtherHeadCode;
20847  }
20848  }
20849  if(MissingcdtUnreportedFlag)
20850  {
20851  TTFile3 << "Possibly missing changes of direction - these should be checked to see if there is a good reason for cdt's not being included. :-\n\n";
20852  }
20853  TTFile3 << LocationNameToBeChecked << " is listed twice with no direction change between in service sequence: " << Sequence << "\n\n";
20854  MarkerList.push_back(FirstInstance);
20855  MarkerList.push_back(SecondInstance);
20856  SingleServiceOutput(0, x, MarkerList, SingleServiceVector, TTFile3);
20857  MissingcdtUnreportedFlag = false;
20858  FullBreak = true; //no more checks for this sequence
20859  break; //out of the a & z loops
20860  }
20861  }
20862  break; // out of the 'z' loop since have checked 'a' as far as need to
20863  }
20864  }
20865  y++;
20866  }
20867  }
20868  if(MissingcdtUnreportedFlag)
20869  {
20870  TTFile3 << "Nothing to report for missing changes of direction\n\n";
20871  }
20872  else
20873  {
20874  TTFile3 << '\n';
20875  }
20876  SequenceLog += "14\n";
20877 
20878 /* Perform the questionable cdt check. Examine each service in the SingleServiceVector, and if don't find the same
20879  name either side of a cdt (before the next cdt) then flag as a questionable cdt.
20880  Method: Have an outer loop for each service that looks for cdts. When found work backwards to the last cdt or beginning and std::list all the
20881  locations excluding the cdt location. Then work forwards to the next cdt or the end and do the same. Sort each list and make unique (duplicated
20882  names on one side of a cdt already checked either by the tt validator or the missing cdt check). Then compare the two lists and if any location
20883  included in both then ok, else report as questionable. If one list is empty then it is reported.
20884 */
20885  typedef std::list<AnsiString> TLocList;
20886  TLocList BackwardList, ForwardList;
20887  bool IntroLineNeeded = true;
20888  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20889  {
20890  unsigned int cdtPosition = 9999;
20891  AnsiString cdtLocation = "";
20892  bool FoundSameName = false;
20893  bool FerEntry = false;
20894  MarkerList.clear();
20895  const TTrainDataEntry &TDEntry = SingleServiceVector.at(x);
20896  for(unsigned int y = 0; y <= TDEntry.ActionVector.size(); y++) // <= because need to examine Fer endings after reached end of vector
20897  // need to check each location name separately in turn, skipped for SignallerControl entries
20898  {
20899  if((y == TDEntry.ActionVector.size()) && !FerEntry)
20900  {
20901  break;
20902  }
20903  BackwardList.clear();
20904  ForwardList.clear();
20905  bool ValidEnd = false;
20906  if(y < TDEntry.ActionVector.size())
20907  {
20908  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
20909  if((AVEntry.FormatType == Repeat) || //end of SSVector, shouldn't be any repeats
20910  (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh") ||
20911  (AVEntry.Command == "Frh-sh"))
20912  {
20913  ValidEnd = true;
20914  }
20915  }
20916  if(FerEntry || ValidEnd)
20917  {
20918  if(MarkerList.empty())
20919  {
20920  break; // out of the 'y' loop since have reached the end & nothing to report
20921  }
20922  else
20923  {
20924  AnsiString Sequence = TDEntry.ServiceReference;
20925  for(unsigned int b = 0; b < TDEntry.ActionVector.size(); b++)
20926  {
20927  if(TDEntry.ActionVector.at(b).Command.SubString(1,3) == "chr")
20928  {
20929  Sequence = Sequence + AnsiString(" -> ") + TDEntry.ActionVector.at(b).OtherHeadCode;
20930  }
20931  }
20932  MarkerList.sort();
20933  if(IntroLineNeeded)
20934  {
20935  TTFile3 << "Questionable change of direction analysis.\n\n";
20936  TTFile3 << "For marked changes of direction there are no same-name locations listed both above (up to the start or another direction change)\n";
20937  TTFile3 << "and below (down to the end or another direction change) but not counting the change of direction location itself.\n\n";
20938  TTFile3 << "These changes of direction are probably valid for movements to and from depots but all should be checked to\n";
20939  TTFile3 << "make sure that none has been included incorrectly:\n\n";
20940  IntroLineNeeded = false;
20941  }
20942  TTFile3 << "Service sequence " << Sequence << " contains questionable changes of direction:-\n\n";
20943  SingleServiceOutput(1, x, MarkerList, SingleServiceVector, TTFile3);
20944  break;
20945  }
20946  }
20947  if(y < TDEntry.ActionVector.size()) //if it is == ...size() then shouldn't have reached here
20948  {
20949  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
20950  if(AVEntry.Command != "cdt")
20951  {
20952  continue; //only looking for cdts
20953  }
20954  //here have found a cdt
20955  cdtPosition = y;
20956  cdtLocation = AVEntry.LocationName;
20957  for(int z = y - 1; z >= 0; z--)
20958  {
20959  const TActionVectorEntry &AVEntry2 = TDEntry.ActionVector.at(z);
20960  if(AVEntry2.Command == "cdt")
20961  {
20962  break; //don't look further back than the last cdt
20963  }
20964  AnsiString LocName = ""; //this section where continuation name assigned for unlocated Snts added at v2.18.0
20965  if((AVEntry2.Command == "Snt") && (AVEntry2.LocationType == EnRoute)) //unlocated Snt - check if the continuation has a name
20966  {
20967  int EntryPos = AVEntry2.RearStartOrRepeatMins; //this is a track vector position
20969  }
20970  if(LocName == "")
20971  {
20972  LocName = AVEntry2.LocationName; //the only un-named values for ActionVectorEntry::LocationName
20973  } //are for unlocated Snts and Fers
20974  if((LocName != "") && (AVEntry2.LocationName != cdtLocation)) //if an earlier entry == cdtLocation will have been picked up in an earlier check
20975  {
20976  BackwardList.push_back(LocName);
20977  }
20978  }
20979  BackwardList.sort();
20980  BackwardList.unique();
20981  for(unsigned int z = y + 1; z < TDEntry.ActionVector.size(); z++)
20982  {
20983  const TActionVectorEntry &AVEntry3 = TDEntry.ActionVector.at(z);
20984  if(/*(AVEntry3.Command == "Fer") || */(AVEntry3.FormatType == Repeat) ||
20985  (AVEntry3.Command == "Fjo") || (AVEntry3.Command == "Frh") ||
20986  (AVEntry3.Command == "Frh-sh") || (AVEntry3.Command == "cdt"))
20987  {
20988  break; // out of the 'z' loop since have reached another cdt or the end
20989  }
20990  AnsiString LocName = "";
20991  if(AVEntry3.Command == "Fer") //this section where continuation name assigned for Fers added at v2.18.0
20992  {
20993  int ExitLoc = AVEntry3.ExitList.front();
20994  LocName = Track->TrackElementAt(1681, ExitLoc).ActiveTrackElementName;
20995  FerEntry = true;
20996  }
20997  else
20998  {
20999  LocName = AVEntry3.LocationName;
21000  }
21001  if((LocName != "") && (AVEntry3.LocationName != cdtLocation)) //if a later entry == cdtLocation will have been picked up in an earlier check
21002  {
21003  ForwardList.push_back(LocName);
21004  }
21005  }
21006  ForwardList.sort();
21007  ForwardList.unique();
21008  FoundSameName = false;
21009  //now have both lists compiled (may be empty) so check for same name in both & report if don't find any
21010  if(!BackwardList.empty() && !ForwardList.empty())
21011  {
21012  for(TLocList::iterator BLIt = BackwardList.begin(); BLIt != BackwardList.end(); BLIt++)
21013  {
21014  for(TLocList::iterator FLIt = ForwardList.begin(); FLIt != ForwardList.end(); FLIt++)
21015  {
21016  if(*BLIt == *FLIt)
21017  {
21018  FoundSameName = true;
21019  }
21020  }
21021  }
21022  }
21023  if(!FoundSameName) //report the inability to find same name
21024  {
21025  MarkerList.push_back(cdtPosition);
21026  }
21027  }
21028  }
21029  }
21030  if(IntroLineNeeded)
21031  {
21032  TTFile3 << "Nothing to report for questionable changes of direction\n\n";
21033  }
21034  else
21035  {
21036  TTFile3 << '\n';
21037  }
21038 /*
21039 //debug section
21040 //print all SSVector for diagnostic purposes
21041  TTFile3 << "Whole SSVector\n\n";
21042  TNumList EmptyList;
21043  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
21044  {
21045  SingleServiceOutput(, x, EmptyList, SingleServiceVector, TTFile3);
21046  }
21047 //end of debug section
21048 */
21049  }
21050  SequenceLog += "15\n";
21051  TTFile3.close();
21052  Utilities->CallLogPop(2212);
21053  return(true);
21054  }
21055 
21056  catch(const Exception &e) //non error catch
21057  {
21058  AnsiString TTErrorFileName = "Analysis Error.txt";
21059  TTErrorFileName = CurDir + "\\Formatted timetables\\" + TTErrorFileName;
21060  std::ofstream TTError(TTErrorFileName.c_str());
21061  if(TTError == 0)
21062  {
21063  ShowMessage("Analysis error file failed to open - can't be created");
21064  Utilities->CallLogPop(2233);
21065  return(false);
21066  }
21067  AnsiString TimeNow = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
21068  TTError << TimeNow.c_str() << '\n' << ArrRange << '\n' << ArrChecked << '\n' << DepRange << '\n' <<
21069  DepChecked << '\n' << AtLocChecked << '\n' << SequenceLog << '\n' << AnsiString(e.Message);
21070 
21071  TTError.close();
21072  ShowMessage("Error in Conflict Analysis: A file called 'Analysis Error.txt' has been created in your Formatted timetables folder. Please send this file together with your railway and timetable files to railwayfeedback@gmail.com for investigation - many thanks");
21073  Utilities->CallLogPop(2226);
21074  return(false);
21075  }
21076 }
21077 
21078 // ---------------------------------------------------------------------------
21079 void TTrainController::SingleServiceOutput(int Caller, int SSVectorNumber, TNumList MarkerList, TTrainDataVector &SingleServiceVector, std::ofstream &VecFile)
21080 {
21081  Utilities->CallLog.push_back(Utilities->TimeStamp() + AnsiString(SSVectorNumber) + ',' + ",SingleServiceOutput");
21082  if((SSVectorNumber < 0) || ((unsigned int)SSVectorNumber >= SingleServiceVector.size()))
21083  {
21084  throw Exception("SSVectorNumber out of range, = " + AnsiString(SSVectorNumber) + ", Vector size = " + SingleServiceVector.size());
21085  }
21086  TTrainDataEntry SingleService = SingleServiceVector.at(SSVectorNumber);
21087  {
21088  VecFile << ",Initial service reference " << SingleService.ServiceReference + '\n';
21089  AnsiString Marker = "";
21090  for(unsigned int x = 0; x < SingleService.ActionVector.size(); x++)
21091  {
21092  Marker = ',';
21093  for(TNumListIterator MLIt = MarkerList.begin(); MLIt != MarkerList.end(); MLIt++)
21094  {
21095  if(int(x) == *MLIt)
21096  {
21097  Marker = "-->,";
21098  break;
21099  }
21100  }
21101  TActionVectorEntry AVE = SingleService.ActionVector.at(x);
21102  if(AVE.FormatType == StartNew)
21103  {
21104  AnsiString RearID = Track->TrackElementAt(1397, AVE.RearStartOrRepeatMins).ElementID;
21105  AnsiString FrontID = Track->TrackElementAt(1398, AVE.FrontStartOrRepeatDigits).ElementID;
21106  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << RearID << ' ' << FrontID << '\n';
21107  }
21108  if(AVE.FormatType == SNTShuttle)
21109  {
21110  AnsiString RearID = Track->TrackElementAt(1399, AVE.RearStartOrRepeatMins).ElementID;
21111  AnsiString FrontID = Track->TrackElementAt(1400, AVE.FrontStartOrRepeatDigits).ElementID;
21112  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << RearID << ' ' << FrontID << ' ' << AVE.OtherHeadCode << '\n';
21113  }
21114  if(AVE.FormatType == SNSShuttle) //should all have been converted to chr
21115  {
21116  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << AVE.OtherHeadCode << ' ' << AVE.NonRepeatingShuttleLinkHeadCode << '\n';
21117  }
21118  if(AVE.FormatType == Repeat) //shouldn't be any repeats, only here to show if any have been copied
21119  {
21120  VecFile << Marker << "Repeat " << AVE.RearStartOrRepeatMins << ' ' << AVE.FrontStartOrRepeatDigits << ' ' << AVE.NumberOfRepeats << '\n';
21121  }
21122  if((AVE.FormatType == TimeCmd) || (AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle) || (AVE.FormatType == FSHNewService))
21123  {
21124  TActionVectorEntry AVHolder = AVE;
21125  if(AVE.Command.SubString(1,3) == "chr")
21126  {
21127  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "sp")
21128  {
21129  AVE.Command = "Change of service to " + AVE.OtherHeadCode + " after split";
21130  AVE.OtherHeadCode = "";
21131  }
21132  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "Fns")
21133  {
21134  AVE.Command = "Change of service to ";
21135  }
21136  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "Fns-sh")
21137  {
21138  AVE.Command = "Change to shuttle finishing service";
21139  }
21140  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "F-nshs")
21141  {
21142  AVE.Command = "Change to shuttle service " + AVE.OtherHeadCode + " from feeder";
21143  AVE.OtherHeadCode = "";
21144  }
21145  }
21146  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << AVE.OtherHeadCode << '\n';
21147  AVE = AVHolder;
21148  }
21149  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
21150  {
21151  VecFile << Marker << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
21152  }
21153  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
21154  {
21155  VecFile << Marker << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
21156  }
21157  else if(AVE.FormatType == TimeTimeLoc)
21158  {
21159  VecFile << Marker << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
21160  }
21161  else if(AVE.FormatType == PassTime)
21162  {
21163  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
21164  }
21165  else if(AVE.FormatType == ExitRailway) //ListOfExits added at v2.10.0
21166  {
21167  AnsiString ListOfExits = "";
21168  for(TNumListIterator NLIt = AVE.ExitList.begin(); NLIt != AVE.ExitList.end(); NLIt++)
21169  {
21170  ListOfExits += AnsiString(Track->TrackElementAt(1432, *NLIt).ElementID) + ' ';
21171  }
21172  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << " Fer " << ListOfExits <<'\n';
21173  }
21174  else if(AVE.FormatType == FinRemHere)
21175  {
21176  VecFile << Marker << "Frh" << '\n';
21177  }
21178  }
21179  VecFile << '\n';
21180  }
21181  Utilities->CallLogPop(2318);
21182 }
21183 
21184 // ---------------------------------------------------------------------------
21185 
21186 TTrainDataEntry TTrainController::GetServiceFromVector(AnsiString Caller, AnsiString HeadCode, TTrainDataVector Vector, bool &FinishType, bool &FoundFlag)
21187 {
21188  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetServiceFromVector," + HeadCode);
21189  FoundFlag = false;
21190  FinishType = true;
21191  for(unsigned int x = 0; x < Vector.size(); x++)
21192  {
21193 // AnsiString ThisRef = Vector.at(x).ServiceReference;
21194  if(Vector.at(x).ServiceReference == HeadCode)
21195  {
21196  if(Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 1).FormatType == Repeat) //shouldn't be any repeats
21197  {
21198  TActionVectorEntry AVE = Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 2);
21199  if((AVE.Command == "Fjo") || (AVE.Command == "Frh") || (AVE.Command == "Fer") || (AVE.Command == "Frh-sh"))
21200  {
21201  FinishType = false;
21202  }
21203  }
21204  else
21205  {
21206  TActionVectorEntry AVE = Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 1);
21207  if((AVE.Command == "Fjo") || (AVE.Command == "Frh") || (AVE.Command == "Fer") || (AVE.Command == "Frh-sh"))
21208  {
21209  FinishType = false;
21210  }
21211  }
21212  FoundFlag = true;
21213  Utilities->CallLogPop(2319);
21214  return(Vector.at(x));
21215  }
21216  }
21217  Utilities->CallLogPop(2320);
21218  return(Vector.at(Vector.size() - 1)); //return last for want of returning something
21219 }
21220 
21221 // ---------------------------------------------------------------------------
21222 
21223 bool TTrainController::WithinTimeRange(int Caller, AnsiString Time1, AnsiString Time2, int MinuteRange) //times are "HH:MM"
21224 {
21225 //convert times to integer minutes
21226  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WithinTimeRange," + Time1 + "," + Time2 + "," + AnsiString(MinuteRange));
21227  if((Time1 == "") || (Time2 == ""))
21228  {
21229  Utilities->CallLogPop(2213);
21230  return(false);
21231  }
21232  int Mins = Time1.SubString(4,2).ToInt();
21233  int Hours = Time1.SubString(1,2).ToInt();
21234  int Time1Mins = (Hours * 60) + Mins;
21235  Mins = Time2.SubString(4,2).ToInt();
21236  Hours = Time2.SubString(1,2).ToInt();
21237  int Time2Mins = (Hours * 60) + Mins;
21238  if(abs(Time1Mins - Time2Mins) <= MinuteRange)
21239  {
21240  Utilities->CallLogPop(2214);
21241  return(true);
21242  }
21243  Utilities->CallLogPop(2215);
21244  return(false);
21245 }
21246 
21247 // ---------------------------------------------------------------------------
21248 
21249 AnsiString TTrainController::ConsolidateSARNTArrDep(int Caller, const AnsiString Input, int &NumTrainsAtLoc, AnsiString Location, bool Arrival,
21250  bool &AnalysisError, int &MaxNumberOfSameDirections)
21251 {
21252  //input consists of services and service Arr or Dep times as a comma separated list, Location needed to determine direction information
21253 
21254  try
21255  {
21256  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ConsolidateSARNTArrDep," + Input);
21257  AnsiString Output = "", OneService = "", TempStr1 = "", TempStr2 = "";
21258  int SCPos = 0;
21259  std::list<AnsiString> ServiceList; //this is the list of services with times extracted from Input - not to be confused with ServiceCallingPointsList
21260  //first change every second comma in Input to a semicolon so can separate services but keep times with services
21261  bool EvenComma = false;
21262  for(int x = 1; x <= Input.Length(); x++)
21263  {
21264  TempStr1 = Input[x];
21265  if(TempStr1 == AnsiString(',') && EvenComma)
21266  {
21267  TempStr2 += ';';
21268  }
21269  else
21270  {
21271  TempStr2 += Input[x];
21272  }
21273  if(TempStr1 == AnsiString(','))
21274  {
21275  EvenComma = !EvenComma;
21276  }
21277  }
21278  //load up the list of services with associated times
21279  while(TempStr2.Length() > 0)
21280  {
21281  SCPos = TempStr2.Pos(';');
21282  if(SCPos > 0) //0 if not found, as won't be when only one service left
21283  {
21284  OneService = TempStr2.SubString(1, SCPos - 1);
21285  ServiceList.push_back(OneService);
21286  TempStr2 = TempStr2.SubString(SCPos + 1, TempStr2.Length() - SCPos);
21287  }
21288  else //no semicolon so looking at last (or only) element
21289  {
21290  ServiceList.push_back(TempStr2);
21291  TempStr2 = "";
21292  }
21293  }
21294  ServiceList.sort(); // alphabetical order
21295  ServiceList.unique(); //remove duplicates
21296  NumTrainsAtLoc = ServiceList.size(); //calc this after removing duplicates as may not have changed
21297 
21298  //now add direction information from AllServiceCallingLocsMap - key is service ref and value a list of calling points in order
21299  int DirectionMarker = 0; //this is added in & runs from 1 upwards, same marker for diff services = same direction
21300  //first add the direction marker "&0" for not yet allocated - '&' is an identifier
21301  std::list<AnsiString>::iterator SLIt, SLIt1, SLIt2, SLIt3;
21302 
21303  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
21304  {
21305  *SLIt = *SLIt + "&0"; //add in a basic direction marker to each service
21306  }
21307  SLIt3 = ServiceList.end();
21308  SLIt3--; //so points to last element
21309  AnsiString ServiceRef1, ServiceRef2, AnsiTime1, AnsiTime2, RepeatInfo1, RepeatInfo2; //1 refers to first for..next loop & 2 to second
21310  int AmpersandPos, SpacePos, CommaPos1, CommaPos2, RepeatNum1, RepeatNum2;
21311  TAllServiceCallingLocsMap::iterator ASCLIt1, ASCLIt2;
21312  TServiceCallingLocsList ServiceCallingLocsList1, ServiceCallingLocsList2;
21313  MaxNumberOfSameDirections = 0; //at end of each SLIt loop if SameDirectionCount > MaxNumberOfSameDirections then MaxNumberOfSameDirections = SameDirectionCount
21314  int SameDirectionCount = 0; //starts at 1 at each SLIt loop (because SLIt1 entry already has a DirectionMarker) and increments for every same direction
21315 
21316  for(std::list<AnsiString>::iterator SLIt1 = ServiceList.begin(); SLIt1 != SLIt3; SLIt1++) //should be end() - 1 but can't use -1 with lists so have to improvise
21317  {
21318  SLIt = SLIt1;
21319  SLIt++; //so points to one after SLIt1
21320  if(SLIt1->SubString(SLIt1->Length() - 1, 2) != AnsiString("&0"))
21321  {
21322  continue; //already allocated so skip to the next
21323  }
21324  else
21325  {
21326  CommaPos1 = SLIt1->Pos(','); //can't be 0
21327  ServiceRef1 = SLIt1->SubString(1, CommaPos1 - 1);
21328  //but this contains "(First service..." etc so need to strip these, but use to extract RepeatNum
21329  SpacePos = ServiceRef1.Pos(' ');
21330  RepeatNum1 = 0;
21331  if(SpacePos > 0) //otherwise it's already correct
21332  {
21333  RepeatInfo1 = ServiceRef1.SubString(SpacePos + 2, ServiceRef1.Length() - SpacePos - 2); //drops the brackets and leaves "First service", "Repeat 2" etc
21334  ServiceRef1 = ServiceRef1.SubString(1, SpacePos - 1);
21335  if(RepeatInfo1[1] == 'F')
21336  {
21337  RepeatNum1 = 0;
21338  }
21339  else
21340  {
21341  SpacePos = RepeatInfo1.Pos(' ');
21342  RepeatNum1 = RepeatInfo1.SubString(SpacePos + 1, RepeatInfo1.Length() - SpacePos).ToInt();
21343  }
21344  }
21345  AnsiTime1 = SLIt1->SubString(CommaPos1 + 1, SLIt1->Length() - CommaPos1);
21346  //but this includes the "&0" etc so need to strip these
21347  AmpersandPos = AnsiTime1.Pos('&');
21348  AnsiTime1 = AnsiTime1.SubString(1, AmpersandPos - 1);
21349 
21350  ASCLIt1 = AllServiceCallingLocsMap.find(ServiceRef1);
21351  if(ASCLIt1 == AllServiceCallingLocsMap.end()) //can't find it
21352  {
21353  throw Exception("ASCLIt1 Error in " + Input);
21354  }
21355  ServiceCallingLocsList1 = ASCLIt1->second;
21356  AmpersandPos = SLIt1->Pos('&');
21357  *SLIt1 = SLIt1->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
21358  *SLIt1 = *SLIt1 + AnsiString(++DirectionMarker); //now add the next marker (pre-increment), allow for it being more than one digit
21359 
21360  SameDirectionCount = 1;
21361  for(SLIt2 = SLIt; SLIt2 != ServiceList.end(); SLIt2++)
21362  {
21363  CommaPos2 = SLIt2->Pos(','); //can't be 0
21364  ServiceRef2 = SLIt2->SubString(1, CommaPos2 - 1);
21365  //but this contains "(First service..." etc so need to strip these
21366  SpacePos = ServiceRef2.Pos(' ');
21367  RepeatNum2 = 0;
21368  if(SpacePos > 0) //otherwise it's already correct
21369  {
21370  RepeatInfo2 = ServiceRef2.SubString(SpacePos + 2, ServiceRef2.Length() - SpacePos - 2); //drops the brackets and leaves "First service", "Repeat 2" etc
21371  ServiceRef2 = ServiceRef2.SubString(1, SpacePos - 1);
21372  if(RepeatInfo2[1] == 'F')
21373  {
21374  RepeatNum2 = 0;
21375  }
21376  else
21377  {
21378  SpacePos = RepeatInfo2.Pos(' ');
21379  RepeatNum2 = RepeatInfo2.SubString(SpacePos + 1, RepeatInfo2.Length() - SpacePos).ToInt();
21380  }
21381  }
21382  AnsiTime2 = SLIt2->SubString(CommaPos2 + 1, SLIt2->Length() - CommaPos2);
21383  //but this includes the "&0" etc so need to strip these
21384  AmpersandPos = AnsiTime2.Pos('&');
21385  AnsiTime2 = AnsiTime2.SubString(1, AmpersandPos - 1);
21386 
21387  ASCLIt2 = AllServiceCallingLocsMap.find(ServiceRef2);
21388  if(ASCLIt2 == AllServiceCallingLocsMap.end()) //can't find it
21389  {
21390  throw Exception("ASCLIt2 Error in " + Input);
21391  }
21392  ServiceCallingLocsList2 = ASCLIt2->second;
21393  //now compare the two
21394  if(SameDirection(0, ServiceRef1, ServiceRef2, AnsiTime1, AnsiTime2, RepeatNum1, RepeatNum2, ServiceCallingLocsList1, ServiceCallingLocsList2, Location, Arrival))
21395  {
21396  int AmpersandPos = SLIt2->Pos('&');
21397  *SLIt2 = SLIt2->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
21398  *SLIt2 = *SLIt2 + AnsiString(DirectionMarker); //now add the same marker as *SLIt1
21399  SameDirectionCount++;
21400  }
21401  }
21402  if(SameDirectionCount > MaxNumberOfSameDirections)
21403  {
21404  MaxNumberOfSameDirections = SameDirectionCount;
21405  }
21406  }
21407  }
21408 
21409  if(SLIt3->SubString(SLIt3->Length() - 1, 2) == AnsiString("&0")) //*SLTIt3 is the last in the list and may not have been allocated, if not it doesn't match
21410  {
21411  //any existing direction so allocate it now
21412  AmpersandPos = SLIt3->Pos('&');
21413  *SLIt3 = SLIt3->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
21414  *SLIt3 = *SLIt3 + AnsiString(++DirectionMarker);
21415  }
21416  //now change direction markers to upper case letters beginning with 'A' (and continuing with 'AA' if exceed 26) & add a comma before so have ServiceRef, DirectionMarker, Time
21417  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
21418  {
21419  //extract the DirectionMarker as an integer
21420  AmpersandPos = SLIt->Pos('&');
21421  AnsiString DirectionMarkerString = SLIt->SubString(AmpersandPos + 1, SLIt->Length() - AmpersandPos); //extract the number as an ansistring
21422  AnsiString ServiceWithoutMarker = SLIt->SubString(1, AmpersandPos - 1); //truncate the &number
21423  DirectionMarker = DirectionMarkerString.ToInt();
21424  AnsiString DirectionSuffix = "";
21425  char c;
21426  if(DirectionMarker < 27)
21427  {
21428  c = 64 + DirectionMarker; //so 1 -> 'A'
21429  DirectionSuffix = "," + AnsiString(c);
21430  }
21431  else if(DirectionMarker < 53)
21432  {
21433  c = 65 + DirectionMarker - 27; //so 27 -> 'AA'
21434  DirectionSuffix = ",A" + AnsiString(c);
21435  }
21436  else
21437  {
21438  DirectionSuffix = ",?"; //shouldn'tn ever get this far!
21439  }
21440  *SLIt = ServiceWithoutMarker + DirectionSuffix;
21441  }
21442  //now prepare the final consolidated output
21443  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
21444  {
21445  Output = Output + *SLIt + ","; //will end up with an unwanted comma at the end
21446  }
21447  if(Output.Length() > 0)
21448  {
21449  Output = Output.SubString(1, Output.Length() - 1); //remove the last comma
21450  }
21451  Utilities->CallLogPop(2216);
21452  return(Output);
21453  }
21454 
21455  catch(const Exception &e) //non error catch
21456  {
21457  AnalysisError = true;
21458  Utilities->CallLogPop(2227);
21459  return(e.Message);
21460  }
21461 }
21462 
21463 // ---------------------------------------------------------------------------
21464 
21465 AnsiString TTrainController::ConsolidateSARNTAtLoc(int Caller, const AnsiString Input, int &NumTrainsAtLoc)
21466 {
21467  //similar to above but doesn't include times in the input
21468  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ConsolidateSARNTAtLoc," + Input);
21469  AnsiString InternalInput = Input, Output = "", OneService = "";
21470  int CommaPos = 0;
21471  std::list<AnsiString> ServiceList;
21472  //load up the list
21473  while(InternalInput.Length() > 0)
21474  {
21475  CommaPos = InternalInput.Pos(',');
21476  if(CommaPos > 0) //0 if not found, as won't be when only one service left
21477  {
21478  OneService = InternalInput.SubString(1, CommaPos - 1);
21479  ServiceList.push_back(OneService);
21480  InternalInput = InternalInput.SubString(CommaPos + 1, InternalInput.Length() - CommaPos);
21481  }
21482  else //no comma so looking at last (or only) element
21483  {
21484  ServiceList.push_back(InternalInput);
21485  InternalInput = "";
21486  }
21487  }
21488 
21489  ServiceList.sort(); // alphabetical order
21490  ServiceList.unique(); //remove duplicates
21491  NumTrainsAtLoc = ServiceList.size(); //calc this after removing duplicates as may not have changed
21492  for(std::list<AnsiString>::iterator SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
21493  {
21494  Output = Output + *SLIt + ","; //will end up with an unwanted comma at the end
21495  }
21496  if(Output.Length() > 0)
21497  {
21498  Output = Output.SubString(1, Output.Length() - 1); //remove the last comma
21499  }
21500  Utilities->CallLogPop(2217);
21501  return(Output);
21502 }
21503 
21504 // ---------------------------------------------------------------------------
21505 
21506 
21507 bool TTrainController::SameDirection(int Caller, AnsiString Ref1In, AnsiString Ref2In, AnsiString Time1, AnsiString Time2, int RepeatNum1, int RepeatNum2, TServiceCallingLocsList List1,
21508  TServiceCallingLocsList List2, AnsiString Location, bool Arrival)
21509 {
21510  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SameDirection," + Ref1In + "," + Ref2In + "," + Time1 + "," + Time2 + "," +
21511  AnsiString(RepeatNum1) + "," + AnsiString(RepeatNum2) + "," + Location);
21512 
21513  std::list<AnsiString>::iterator LP1, LP2, ListPtr1, ListPtr2, LocPtr1, LocPtr2; //LP1 & 2 are temporary pointers, ListPtrs are
21514  //general list pointers, LocPtrs point to Location in the two lists
21515 
21516  //first find the relevant values for LocPtr1 & LocPtr2 taking account of cdts and times
21517  //for List1
21518  bool LocFound = false;
21519  AnsiString Ref1 = Ref1In, Ref2 = Ref2In;
21520  int IncMinutes;
21521  TDateTime FirstServiceTime;
21522 
21523  //first need to strip off /1, /2 etc if present from Ref1 & Ref2 (leave Ref1In & Ref2In for error message & retain value as target in finding the correct reference for cdts)
21524  int Ref1Target = 0, Ref1Count = 0;
21525  int Ref2Target = 0, Ref2Count = 0;
21526 
21527 /* drop this after retained slashes in ServiceRef
21528  int SlashPos = Ref1.Pos('/');
21529  if(SlashPos > 0) //if 0 Ref1 == Ref1In & target stays at 0
21530  {
21531  Ref1Target = Ref1.SubString(SlashPos + 1, Ref1.Length() - SlashPos).ToInt();
21532  Ref1 = Ref1.SubString(1, SlashPos - 1); //truncate up to but omit '/'
21533  }
21534  int Ref2Target = 0, Ref2Count = 0;
21535  SlashPos = Ref2.Pos('/');
21536  if(SlashPos > 0) //if 0 leave as is
21537  {
21538  Ref2Target = Ref2.SubString(SlashPos + 1, Ref2.Length() - SlashPos).ToInt();
21539  Ref2 = Ref2.SubString(1, SlashPos - 1); //truncate up to but omit '/'
21540  }
21541 */
21542 
21543  for(ListPtr1 = List1.begin(); ListPtr1 != List1.end(); ListPtr1++) //note that when this routine entered Ref1In & Ref2In are already set to the correct services,
21544  {
21545  //even if others have same names. But if there are cdt's then need to refind the correct service
21546  if((*ListPtr1) == Location) //
21547  {
21548  LocPtr1 = ListPtr1; //may be modified later
21549  LocFound = true;
21550  }
21551  if(ListPtr1->SubString(1, 3) == "%%%")
21552  {
21553  AnsiString CDTTime = ListPtr1->SubString(4, 5);
21554  //now adjust the time to correspond to the repeat if there is one
21555  if(RepeatNum1 > 0) //if it is 0 then AnsiTime1 is already valid
21556  {
21557  IncMinutes = -1;
21558  FirstServiceTime = TDateTime(-1);
21559  bool BreakFlag = false;
21560  for(TTrainDataVector::iterator TDVIt = TrainDataVectorCopy.begin(); TDVIt != TrainDataVectorCopy.end(); TDVIt++)
21561  {
21562  if(TDVIt->ServiceReference == Ref1)
21563  {
21564  if(Ref1Target > Ref1Count)
21565  {
21566  Ref1Count++;
21567  continue;
21568  }
21569  IncMinutes = TDVIt->ActionVector.back().RearStartOrRepeatMins;
21570  for(TActionVector::iterator AVIt = TDVIt->ActionVector.begin(); AVIt != TDVIt->ActionVector.end(); AVIt++)
21571  {
21572  if(Utilities->Format96HHMM(AVIt->EventTime) == CDTTime)
21573  {
21574  FirstServiceTime = AVIt->EventTime; //i.e. the FirstService value of CDTTime
21575  BreakFlag = true;
21576  break;
21577  }
21578  if(Utilities->Format96HHMM(AVIt->ArrivalTime) == CDTTime) //add arr & dep in case find sooner (though dep shouldn't be sooner)
21579  {
21580  FirstServiceTime = AVIt->ArrivalTime;
21581  BreakFlag = true;
21582  break;
21583  }
21584  if(Utilities->Format96HHMM(AVIt->DepartureTime) == CDTTime)
21585  {
21586  FirstServiceTime = AVIt->DepartureTime;
21587  BreakFlag = true;
21588  break;
21589  }
21590  }
21591  if(BreakFlag)
21592  {
21593  break;
21594  }
21595  }
21596  }
21597  if(IncMinutes == -1)
21598  {
21599  throw Exception("Failed to find service for ServiceRef1 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21600  }
21601  if(FirstServiceTime == TDateTime(-1))
21602  {
21603  throw Exception("Failed to find first service time for ServiceRef1 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21604  }
21605  CDTTime = Utilities->Format96HHMM(TrainController->GetRepeatTime(60, FirstServiceTime, RepeatNum1, IncMinutes));
21606  }
21607  if(!Arrival && (Time1 == CDTTime)) //continue if equal in case next is a departure for the Location
21608  {
21609  LocFound = false;
21610  continue;
21611  }
21612  if(Arrival && (Time1 == CDTTime)) //gone far enough so can stop
21613  {
21614  break;
21615  }
21616  if(Time1 > CDTTime) //not there yet so go on
21617  {
21618  LocFound = false;
21619  continue;
21620  }
21621  if(Time1 < CDTTime) //gone too far so can stop now
21622  {
21623  break;
21624  }
21625  }
21626  }
21627  if(!LocFound) //have to find it in both lists
21628  {
21629  Utilities->CallLogPop(2228);
21630  return( false);
21631  }
21632  //for List2
21633  LocFound = false;
21634  for(ListPtr2 = List2.begin(); ListPtr2 != List2.end(); ListPtr2++)
21635  {
21636  if((*ListPtr2) == Location)
21637  {
21638  LocPtr2 = ListPtr2; //may be modified later
21639  LocFound = true;
21640  }
21641  if(ListPtr2->SubString(1, 3) == "%%%")
21642  {
21643  AnsiString CDTTime = ListPtr2->SubString(4, 5);
21644  //now adjust the time to correspond to the repeat if there is one
21645  if(RepeatNum2 > 0) //if it is 0 then AnsiTime1 is already valid
21646  {
21647  IncMinutes = -1;
21648  FirstServiceTime = TDateTime(-1);
21649  bool BreakFlag = false;
21650  for(TTrainDataVector::iterator TDVIt = TrainDataVectorCopy.begin(); TDVIt != TrainDataVectorCopy.end(); TDVIt++)
21651  {
21652  if(TDVIt->ServiceReference == Ref2)
21653  {
21654  if(Ref2Target > Ref2Count)
21655  {
21656  Ref2Count++;
21657  continue;
21658  }
21659  IncMinutes = TDVIt->ActionVector.back().RearStartOrRepeatMins;
21660  for(TActionVector::iterator AVIt = TDVIt->ActionVector.begin(); AVIt != TDVIt->ActionVector.end(); AVIt++)
21661  {
21662  if(Utilities->Format96HHMM(AVIt->EventTime) == CDTTime)
21663  {
21664  FirstServiceTime = AVIt->EventTime;
21665  BreakFlag = true;
21666  break;
21667  }
21668  if(Utilities->Format96HHMM(AVIt->ArrivalTime) == CDTTime)
21669  {
21670  FirstServiceTime = AVIt->ArrivalTime;
21671  BreakFlag = true;
21672  break;
21673  }
21674  if(Utilities->Format96HHMM(AVIt->DepartureTime) == CDTTime)
21675  {
21676  FirstServiceTime = AVIt->DepartureTime;
21677  BreakFlag = true;
21678  break;
21679  }
21680  }
21681  if(BreakFlag)
21682  {
21683  break;
21684  }
21685  }
21686  }
21687  if(IncMinutes == -1)
21688  {
21689  throw Exception("IncMinutes -1 for ServiceRef2 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21690  }
21691  if(FirstServiceTime == TDateTime(-1))
21692  {
21693  throw Exception("First service time -1 for ServiceRef2 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21694  }
21695  CDTTime = Utilities->Format96HHMM(TrainController->GetRepeatTime(61, FirstServiceTime, RepeatNum2, IncMinutes));
21696  }
21697  if(!Arrival && (Time2 == CDTTime)) //continue if equal in case next is a departure for the Location
21698  {
21699  LocFound = false;
21700  continue;
21701  }
21702  if(Arrival && (Time2 == CDTTime)) //gone far enough so can stop
21703  {
21704  break;
21705  }
21706  if(Time2 > CDTTime) //not there yet so go on
21707  {
21708  LocFound = false;
21709  continue;
21710  }
21711  if(Time2 < CDTTime) //gone too far so can stop now
21712  {
21713  break;
21714  }
21715  }
21716  }
21717  if(!LocFound) //have to find it in both lists, and should be found but allow for it not being
21718  {
21719  Utilities->CallLogPop(2229);
21720  return( false);
21721  }
21722  //now, for the arrival analysis, see if there is a common location before the LocPtrs & within any cdts, and if so return true, else return false
21723  //set ListPtr1 to the search start position
21724  if(Arrival)
21725  {
21726  LP1 = List1.begin();
21727  LP1--; //now points to before the first entry
21728  for(ListPtr1 = LocPtr1; ListPtr1 != LP1; ListPtr1--) //search backwards from Location
21729  {
21730  if(ListPtr1 == List1.begin())
21731  {
21732  break;
21733  }
21734  if(ListPtr1->SubString(1, 3) == "%%%") //a cdt event
21735  {
21736  ListPtr1++; //point to one past the cdt
21737  break;
21738  }
21739  }
21740  //set ListPtr2 to the search start position
21741  LP2 = List2.begin();
21742  LP2--; //now points to before the first entry
21743  for(ListPtr2 = LocPtr2; ListPtr2 != LP2; ListPtr2--)
21744  {
21745  if(ListPtr2 == List2.begin())
21746  {
21747  break;
21748  }
21749  if(ListPtr2->SubString(1, 3) == "%%%") //a cdt event
21750  {
21751  ListPtr2++; //point to one past the cdt
21752  break;
21753  }
21754  }
21755  //ListPtr1 & 2 now at search start position
21756  LP1 = ListPtr1;
21757  LP2 = ListPtr2;
21758  //now search forwards, i.e. for common locations before Location
21759  for(ListPtr1 = LP1; ListPtr1 != List1.end(); ListPtr1++)
21760  {
21761  if(ListPtr1 == LocPtr1) //reached Location without finding a common earlier location so skip to the backwards check
21762  {
21763  break;
21764  }
21765  if(ListPtr1->SubString(1, 3) == "%%%") //reached a cdt event without finding a common earlier location
21766  {
21767  break;
21768  }
21769  for(ListPtr2 = LP2; ListPtr2 != List2.end(); ListPtr2++)
21770  {
21771  if(ListPtr2 == LocPtr2) //not found common earlier location so go to the next ListPtr1
21772  {
21773  break;
21774  }
21775  if(ListPtr2->SubString(1, 3) == "%%%") //reached a cdt event without finding a common earlier location so go to the next ListPtr1
21776  {
21777  break;
21778  }
21779  if((*ListPtr1) == (*ListPtr2)) //found a common earlier location
21780  {
21781  Utilities->CallLogPop(2230);
21782  return( true);
21783  }
21784  }
21785  }
21786  }
21787 
21788  //now, for the departure analysis, reset the start positions and search locations after Location
21789 
21790  else
21791  {
21792  LP1 = LocPtr1;
21793  LP1++; //start at one past the location itself
21794  LP2 = LocPtr2;
21795  LP2++;
21796  for(ListPtr1 = LP1; ListPtr1 != List1.end(); ListPtr1++)
21797  {
21798  if(ListPtr1 == List1.end()) //reached end point so stop
21799  {
21800  break;
21801  }
21802  if(ListPtr1->SubString(1, 3) == "%%%") //reached a cdt event without finding a common location
21803  {
21804  break;
21805  }
21806  for(ListPtr2 = LP2; ListPtr2 != List2.end(); ListPtr2++)
21807  {
21808  if(ListPtr2 == List2.end()) //reached end point so go to next ListPtr1
21809  {
21810  break;
21811  }
21812  if(ListPtr2->SubString(1, 3) == "%%%") //reached a cdt event without finding a common location so go to the next ListPtr1
21813  {
21814  break;
21815  }
21816  if((*ListPtr1) == (*ListPtr2)) //found a common later location
21817  {
21818  Utilities->CallLogPop(2231);
21819  return( true);
21820  }
21821  }
21822  }
21823  }
21824  Utilities->CallLogPop(2232);
21825  return( false);
21826 }
21827 
21828 // ---------------------------------------------------------------------------
21829 
21830 AnsiString TTrainController::GetExitLocationAndAt(int Caller, TNumList &ExitList, AnsiString &AllowedExits) const
21831 {
21832  // changed at v2.7.0 to show allowable exit elements
21833  if(ExitList.empty())
21834  {
21835  return("");
21836  }
21837  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetExitLocationAndAt");
21838  AnsiString StartName = Track->TrackElementAt(735, *(ExitList.begin())).ActiveTrackElementName;
21839  AnsiString ExitLocList = "";
21840  AllowedExits = "";
21841 
21842  unsigned int Counter = 0;
21843  for(TNumListIterator ELIt = ExitList.begin(); ELIt != ExitList.end(); ELIt++)
21844  {
21845  ExitLocList += Track->TrackElementAt(1018, *ELIt).ElementID + " ";
21846  Counter++;
21847  if(((Counter % 6) == 0) && (Counter < (ExitList.size() - 1))) // only add a newline if more to come
21848  {
21849  ExitLocList += "\n";
21850  }
21851  }
21852  if(StartName == "")
21853  {
21854  if(ExitList.size() == 1)
21855  {
21856  AnsiString ID = Track->TrackElementAt(738, *(ExitList.begin())).ElementID;
21857  Utilities->CallLogPop(1571);
21858  return(" at " + ID);
21859  }
21860  else
21861  {
21862  Utilities->CallLogPop(1572);
21863  if(ExitList.size() < 4)
21864  {
21865  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21866  return("");
21867  }
21868  else
21869  {
21870  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21871  return("");
21872  }
21873  }
21874  }
21875  for(TNumListIterator ELIT = ExitList.begin(); ELIT != ExitList.end(); ELIT++)
21876  {
21877  if(Track->TrackElementAt(736, *ELIT).ActiveTrackElementName != StartName)
21878  {
21879  Utilities->CallLogPop(1570);
21880  if(ExitList.size() < 4)
21881  {
21882  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21883  return("");
21884  }
21885  else
21886  {
21887  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21888  return("");
21889  }
21890  }
21891  }
21892  Utilities->CallLogPop(1569);
21893  if(ExitList.size() < 4)
21894  {
21895  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21896  return(" at " + StartName);
21897  }
21898  else
21899  {
21900  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21901  return(" at " + StartName);
21902  }
21903 }
21904 
21905 // ---------------------------------------------------------------------------
21906 /* can't trust this as locations within a vector may not be contiguous
21907  bool TTrainController::IsServiceTerminating(int Caller, TTrainDataEntry *TDEPtr, TActionVectorEntry *AVPtr)
21908  {
21909  //Search ActionVector from the position after the entry value for Ptr to the end, and return true if find a Finish
21910  //entry before Fer or TimeLoc. No point checking for TimeTimeLoc since at a stop location now so a later TimeTimeLoc
21911  //must be preceded by a TimeLoc departure
21912  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsServiceTerminating");
21913  for(unsigned int x=1;x<TDEPtr->ActionVector.size();x++)
21914  {
21915  if((AVPtr + x) < TDEPtr->ActionVector.end())
21916  {
21917  AnsiString xx = (AVPtr + x)->Command;//test
21918  TTimetableFormatType xy = (AVPtr + x)->FormatType;//test
21919  TTimetableSequenceType xz = (AVPtr + x)->SequenceType;//test
21920  if(((AVPtr + x)->Command == "Fer") || ((AVPtr + x)->FormatType == TimeLoc))
21921  {
21922  Utilities->CallLogPop();
21923  return false;
21924  }
21925  else if((AVPtr + x)->SequenceType == FinishSequence)
21926  {
21927  Utilities->CallLogPop();
21928  return true;
21929  }
21930  }
21931  }
21932  Utilities->CallLogPop();
21933  return false;
21934  }
21935 */
21936 // ---------------------------------------------------------------------------
21937 
21938 void TTrainController::SendPerformanceSummary(int Caller, std::ofstream &PerfFile)
21939 {
21940  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SendPerformanceSummary");
21941  AnsiString FormatStr = "####0.0";
21942  AnsiString AvLateArrMins = "";
21943  AnsiString AvEarlyArrMins = "";
21944  AnsiString AvLatePassMins = "";
21945  AnsiString AvEarlyPassMins = "";
21946  AnsiString AvLateDepMins = "";
21947  AnsiString AvLateExitMins = "";
21948  AnsiString AvEarlyExitMins = "";
21949 
21950  //calculate remaining CumulativeDelayedRandMinsAllTrains for trains still in vector (CumulativeDelayedRandMinsAllTrains for exited or removed trains already accounted for)
21951  for(unsigned int x = 0; x < TrainVector.size(); x++)
21952  {
21953  Utilities->CumulativeDelayedRandMinsAllTrains += int(TrainVectorAt(89, x).CumulativeDelayedRandMinsOneTrain);
21954  }
21955 
21956  if(LateArrivals > 0)
21957  {
21958  AvLateArrMins = FormatFloat(FormatStr, (TotLateArrMins / LateArrivals));
21959  }
21960  if(EarlyArrivals > 0)
21961  {
21962  AvEarlyArrMins = FormatFloat(FormatStr, (TotEarlyArrMins / EarlyArrivals));
21963  }
21964  if(LatePasses > 0)
21965  {
21966  AvLatePassMins = FormatFloat(FormatStr, (TotLatePassMins / LatePasses));
21967  }
21968  if(EarlyPasses > 0)
21969  {
21970  AvEarlyPassMins = FormatFloat(FormatStr, (TotEarlyPassMins / EarlyPasses));
21971  }
21972  if(LateDeps > 0)
21973  {
21974  AvLateDepMins = FormatFloat(FormatStr, (TotLateDepMins / LateDeps));
21975  }
21976  if(LateExits > 0) //added at v2.9.1
21977  {
21978  AvLateExitMins = FormatFloat(FormatStr, (TotLateExitMins / LateExits));
21979  }
21980  if(EarlyExits > 0) //added at v2.9.1
21981  {
21982  AvEarlyExitMins = FormatFloat(FormatStr, (TotEarlyExitMins / EarlyExits));
21983  }
21984  PerfFile << '\n' << '\n' << "***************************************";
21985  PerfFile << '\n' << '\n' << "Performance summary:" << '\n';
21986 
21987  if(OnTimeArrivals != 1)
21988  {
21989  PerfFile << OnTimeArrivals << " on-time arrivals" << '\n';
21990  }
21991  else
21992  {
21993  PerfFile << OnTimeArrivals << " on-time arrival" << '\n';
21994  }
21995  if(LateArrivals > 1)
21996  {
21997  PerfFile << LateArrivals << " late arrivals (average " << AvLateArrMins.c_str() << " min)" << '\n';
21998  }
21999  else if(LateArrivals == 1)
22000  {
22001  PerfFile << LateArrivals << " late arrival (" << AvLateArrMins.c_str() << " min)" << '\n';
22002  }
22003  else
22004  {
22005  PerfFile << LateArrivals << " late arrivals" << '\n';
22006  }
22007  if(EarlyArrivals > 1)
22008  {
22009  PerfFile << EarlyArrivals << " early arrivals (average " << AvEarlyArrMins.c_str() << " min)" << '\n';
22010  }
22011  else if(EarlyArrivals == 1)
22012  {
22013  PerfFile << EarlyArrivals << " early arrival (" << AvEarlyArrMins.c_str() << " min)" << '\n';
22014  }
22015  else
22016  {
22017  PerfFile << EarlyArrivals << " early arrivals" << '\n';
22018  }
22019  if(OnTimePasses != 1)
22020  {
22021  PerfFile << OnTimePasses << " on-time passes" << '\n';
22022  }
22023  else
22024  {
22025  PerfFile << OnTimePasses << " on-time pass" << '\n';
22026  }
22027  if(LatePasses > 1)
22028  {
22029  PerfFile << LatePasses << " late passes (average " << AvLatePassMins.c_str() << " min)" << '\n';
22030  }
22031  else if(LatePasses == 1)
22032  {
22033  PerfFile << LatePasses << " late pass (" << AvLatePassMins.c_str() << " min)" << '\n';
22034  }
22035  else
22036  {
22037  PerfFile << LatePasses << " late passes" << '\n';
22038  }
22039  if(EarlyPasses > 1)
22040  {
22041  PerfFile << EarlyPasses << " early passes (average " << AvEarlyPassMins.c_str() << " min)" << '\n';
22042  }
22043  else if(EarlyPasses == 1)
22044  {
22045  PerfFile << EarlyPasses << " early pass (" << AvEarlyPassMins.c_str() << " min)" << '\n';
22046  }
22047  else
22048  {
22049  PerfFile << EarlyPasses << " early passes" << '\n';
22050  }
22051 
22052  if(OnTimeExits != 1) //this batch added at v2.9.1
22053  {
22054  PerfFile << OnTimeExits << " on-time exits" << '\n';
22055  }
22056  else
22057  {
22058  PerfFile << OnTimeExits << " on-time exit" << '\n';
22059  }
22060  if(LateExits > 1)
22061  {
22062  PerfFile << LateExits << " late exits (average " << AvLateExitMins.c_str() << " min)" << '\n';
22063  }
22064  else if(LateExits == 1)
22065  {
22066  PerfFile << LateExits << " late exit (" << AvLateExitMins.c_str() << " min)" << '\n';
22067  }
22068  else
22069  {
22070  PerfFile << LateExits << " late exits" << '\n';
22071  }
22072  if(EarlyExits > 1)
22073  {
22074  PerfFile << EarlyExits << " early exits (average " << AvEarlyExitMins.c_str() << " min)" << '\n';
22075  }
22076  else if(EarlyExits == 1)
22077  {
22078  PerfFile << EarlyExits << " early exit (" << AvEarlyExitMins.c_str() << " min)" << '\n';
22079  }
22080  else
22081  {
22082  PerfFile << EarlyExits << " early exits" << '\n';
22083  }
22084 
22085  if(OnTimeDeps != 1)
22086  {
22087  PerfFile << OnTimeDeps << " on-time departures" << '\n';
22088  }
22089  else
22090  {
22091  PerfFile << OnTimeDeps << " on-time departure" << '\n';
22092  }
22093  if(LateDeps > 1)
22094  {
22095  PerfFile << LateDeps << " late departures (average " << AvLateDepMins.c_str() << " min)" << '\n';
22096  }
22097  else if(LateDeps == 1)
22098  {
22099  PerfFile << LateDeps << " late departure (" << AvLateDepMins.c_str() << " min)" << '\n';
22100  }
22101  else
22102  {
22103  PerfFile << LateDeps << " late departures" << '\n';
22104  }
22105  TDateTime TempExcessLCDownTime;
22106  for(unsigned int x = 0; x < Track->BarriersDownVector.size(); x++) //added at v2.6.0 - should have been added earlier
22107  {
22108 // if(Track->BarriersDownVector.at(x).ReducedTimePenalty) //assume train still to cross LC as probably will, else have false high value & can have
22109  //later perf summaries with lower values, changed at v2.8.0
22110 // {
22111  TempExcessLCDownTime = TrainController->TTClockTime - Track->BarriersDownVector.at(x).StartTime - TDateTime(180.0 / 86400);
22112 // }
22113 /*
22114  else
22115  {
22116  TempExcessLCDownTime = TrainController->TTClockTime - Track->BarriersDownVector.at(x).StartTime;
22117  }
22118 */
22119  if(TempExcessLCDownTime > TDateTime(0))
22120  {
22121  TrainController->ExcessLCDownMins += (double(TempExcessLCDownTime) * 1440);
22122  }
22123  }
22124 
22125  AnsiString FormattedExcessLCDownMins = FormatFloat(FormatStr, ExcessLCDownMins);
22126 
22127  if(ExcessLCDownMins > 0.1)
22128  {
22129  PerfFile << FormattedExcessLCDownMins.c_str() << " excess minutes of level crossing barrier down time" << '\n';
22130  }
22131  else
22132  {
22133  ExcessLCDownMins = 0; //added at v2.16.1 so doesn't count towards performance score if < 0.1mins (else can have low score with no excess mins recorded)
22134  }
22135  if(Utilities->CumulativeDelayedRandMinsAllTrains > 0) //added at v2.13.0
22136  {
22137  PerfFile << Utilities->CumulativeDelayedRandMinsAllTrains << " minutes lost due to random delays when stopped at locations" << '\n';
22138  }
22139  if(MissedStops != 1)
22140  {
22141  PerfFile << MissedStops << " missed stops" << '\n';
22142  }
22143  else
22144  {
22145  PerfFile << MissedStops << " missed stop" << '\n';
22146  }
22147  if(OtherMissedEvents != 1)
22148  {
22149  PerfFile << OtherMissedEvents << " other missed events" << '\n';
22150  }
22151  else
22152  {
22153  PerfFile << OtherMissedEvents << " other missed event" << '\n';
22154  }
22155  if(SkippedTTEvents != 1)
22156  {
22157  PerfFile << SkippedTTEvents << " skipped timetable events" << '\n';
22158  }
22159  else
22160  {
22161  PerfFile << SkippedTTEvents << " skipped timetable event" << '\n';
22162  }
22163  if(UnexpectedExits != 1)
22164  {
22165  PerfFile << UnexpectedExits << " unexpected train exits" << '\n';
22166  }
22167  else
22168  {
22169  PerfFile << UnexpectedExits << " unexpected train exit" << '\n';
22170  }
22171  if(IncorrectExits != 1)
22172  {
22173  PerfFile << IncorrectExits << " incorrect train exits" << '\n';
22174  }
22175  else
22176  {
22177  PerfFile << IncorrectExits << " incorrect train exit" << '\n';
22178  }
22179  if(NumFailures != 1)
22180  {
22181  PerfFile << NumFailures << " train failures" << '\n';
22182  }
22183  else
22184  {
22185  PerfFile << NumFailures << " train failure" << '\n';
22186  }
22187  if(AvHoursIntValue > 0)
22188  {
22189  if(AvHoursIntValue == 1)
22190  {
22191  PerfFile << AvHoursIntValue << " hour mean time betweeen train failures" << '\n';
22192  }
22193  else
22194  {
22195  PerfFile << AvHoursIntValue << " hours mean time betweeen train failures" << '\n';
22196  }
22197  }
22198  AnsiString AvLateMinsLocsNotReached = "";
22199 
22201  int LocsNotReached = (NotStartedTrainLateArr + OperatingTrainLateArr); //dropped divide by 2 after 2.7.0 as don't count late departures as 'failed to arrive'
22202  // each location has an arrival and departure (generally) so divide by 2 - no, dropped after 2.7.0
22203 
22204  if(LocsNotReached > 0)
22205  {
22206  AvLateMinsLocsNotReached = FormatFloat(FormatStr, (OperatingTrainLateMins + NotStartedTrainLateMins) / (NotStartedTrainLateArr + OperatingTrainLateArr));
22207  PerfFile << LocsNotReached << " locations that trains failed to reach (average lateness " << AvLateMinsLocsNotReached.c_str() << " min)" << '\n';
22208  }
22209  if(SPADRisks != 1)
22210  {
22211  PerfFile << SPADRisks << " SPAD risks" << '\n';
22212  }
22213  else
22214  {
22215  PerfFile << SPADRisks << " SPAD risk" << '\n';
22216  }
22217  if(SPADEvents != 1)
22218  {
22219  PerfFile << SPADEvents << " SPADs" << '\n';
22220  }
22221  else
22222  {
22223  PerfFile << SPADEvents << " SPAD" << '\n';
22224  }
22225  if(Derailments != 1)
22226  {
22227  PerfFile << Derailments << " derailments" << '\n';
22228  }
22229  else
22230  {
22231  PerfFile << Derailments << " derailment" << '\n';
22232  }
22233  if(CrashedTrains != 1)
22234  {
22235  PerfFile << CrashedTrains << " crashed trains" << '\n';
22236  }
22237  else
22238  {
22239  PerfFile << CrashedTrains << " crashed train" << '\n';
22240  }
22241  PerfFile << '\n' << "***************************************" << '\n';
22242 
22243  bool DerailSPADFlag = false, CrashFlag = false;
22244 
22245  int OverallScorePercent = 100;
22246  int TotArrDepExit = 0;
22247  double TotLateMinsFactor = 1;
22248  double MissedStopAndSPADRiskFactor = 1;
22249  double NetNegFactor = 1;
22250 
22252  EarlyExits + LateExits + OnTimeExits; //exits added at v2.9.1, passes not counted
22253  // TotArrDep: total number of arrivals & departures including those for trains that haven't reached their destinations yet and are late
22254  // changed at v1.1.4 - calc was inside "if(OverallScorePercent == 100).." block so could remain 0 for SPADs & crashes, & then received the
22255  // 'no timetabled departures... message, which was inappropriate
22256 
22257  if((SPADEvents > 0) || (Derailments > 0))
22258  {
22259  OverallScorePercent = 5; // overrides other calculations
22260  DerailSPADFlag = true;
22261  }
22262  if(CrashedTrains > 0)
22263  {
22264  OverallScorePercent = 0; // overrides other calculations
22265  CrashFlag = true;
22266  }
22267  if(OverallScorePercent == 100)
22268  {
22269  int LatenessPenalty = TotLateArrMins + TotLateDepMins; //added at v2.13.0 for random delays
22270  if(Utilities->CumulativeDelayedRandMinsAllTrains > LatenessPenalty)
22271  {
22272  LatenessPenalty = 0;
22273  }
22274  else
22275  {
22276  LatenessPenalty -= Utilities->CumulativeDelayedRandMinsAllTrains;
22277  }
22278  if(TotArrDepExit > 0)
22279  {
22280  TotLateMinsFactor = exp((-0.1732) * (LatenessPenalty + OperatingTrainLateMins + NotStartedTrainLateMins + TotLateExitMins +
22281  ((OtherMissedEvents + SkippedTTEvents + UnexpectedExits + ExcessLCDownMins) * 15)) / TotArrDepExit); //exits added at v2.9.1
22282  // TotLateMinsFactor: negative exponential factor based on overall average arr & dep minutes late (with OtherMissedEvents & UnexpectedExits
22283  // counting as 15 mins late each), where 4 mins late average = half, 8 mins late = a quarter etc
22284  MissedStopAndSPADRiskFactor = exp((-17.33) * (MissedStops + SPADRisks + IncorrectExits) / TotArrDepExit);
22285  // MissedEventAndSPADRiskFactor: negative exponential factor based on number of missed stops, SPAD risks & IncorrectExits as a proportion
22286  // of arrivals & departures, where 4% = half, 8% = a quarter etc
22287  NetNegFactor = TotLateMinsFactor * MissedStopAndSPADRiskFactor;
22288  // NetNegfactor: product of the above two
22289  OverallScorePercent = 100 * NetNegFactor;
22290  }
22291  }
22292  if((TotArrDepExit > 0) || DerailSPADFlag || CrashFlag)
22293  // flag condits added at v1.1.4 - see above for what the error was
22294  {
22295  AnsiString OneFailureString = ", though the failure would account for some poor performance";
22296  AnsiString TwoOrMoreFailureString = ", though the failures would account for some poor performance";
22297  AnsiString AddedString = "";
22298  if(NumFailures == 1)
22299  {
22300  AddedString = OneFailureString;
22301  }
22302  if(NumFailures > 1)
22303  {
22304  AddedString = TwoOrMoreFailureString;
22305  }
22306  PerfFile << "\nOverall score: " << OverallScorePercent << "%\n";
22307  AnsiString Rating = "";
22308  if(OverallScorePercent == 100)
22309  {
22310  Rating = "Perfect!";
22311  }
22312  else if(OverallScorePercent >= 95)
22313  {
22314  Rating = "Excellent";
22315  }
22316  else if(OverallScorePercent >= 90)
22317  {
22318  Rating = "Very good";
22319  }
22320  else if(OverallScorePercent >= 80)
22321  {
22322  Rating = "Good";
22323  }
22324  else if(OverallScorePercent >= 70)
22325  {
22326  Rating = "Fair";
22327  }
22328  else if(OverallScorePercent >= 60)
22329  {
22330  Rating = "Unacceptable" + AddedString;
22331  }
22332  else if(OverallScorePercent >= 50)
22333  {
22334  Rating = "Poor" + AddedString;
22335  }
22336  else if(OverallScorePercent >= 40)
22337  {
22338  Rating = "Bad" + AddedString;
22339  }
22340  else if(OverallScorePercent >= 30)
22341  {
22342  Rating = "Very bad" + AddedString;
22343  }
22344  else if(OverallScorePercent >= 20)
22345  {
22346  Rating = "Terrible" + AddedString;
22347  }
22348  else if(OverallScorePercent >= 10)
22349  {
22350  Rating = "Appalling" + AddedString;
22351  }
22352  else if(OverallScorePercent >= 5)
22353  {
22354  if(DerailSPADFlag)
22355  {
22356  Rating = "Disastrous - potential loss of life";
22357  }
22358  // SPADs/Derailments
22359  else
22360  {
22361  Rating = "Dire" + AddedString;
22362  }
22363  }
22364  else if(OverallScorePercent < 5)
22365  {
22366  if(CrashFlag)
22367  {
22368  Rating = "Catastrophic - loss of life"; // Crashes
22369  }
22370  else
22371  {
22372  Rating = "Abysmal";
22373  }
22374  }
22375  PerfFile << "Overall rating: " << Rating.c_str() << '\n';
22376  }
22377  else
22378  {
22379  PerfFile << "\nThere were no timetabled departures, arrivals or exits so there is insufficient information to provide a performance score or rating" << '\n';
22380  }
22381  PerfFile << '\n' << "***************************************";
22382  PerfFile.flush();
22383  Utilities->CallLogPop(1736);
22384 }
22385 
22386 // ---------------------------------------------------------------------------
22387 
22389 {
22390  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetWarningFlags");
22391  for(unsigned int x = 0; x < TrainVector.size(); x++)
22392  {
22393  TTrain &Train = TrainVectorAt(58, x);
22394  if(Train.Crashed)
22395  // can't use background colours for crashed & derailed because same colour
22396  {
22397  CrashWarning = true;
22398  }
22399  else if(Train.Derailed)
22400  // can't use background colours for crashed & derailed because same colour
22401  {
22402  DerailWarning = true;
22403  }
22404  else if(Train.BackgroundColour == clSPADBackground)
22405  // use colour as that changes as soon as passes signal
22406  {
22407  SPADWarning = true;
22408  }
22409  else if(Train.BackgroundColour == clTrainFailedBackground)
22410  {
22411  TrainFailedWarning = true;
22412  }
22413  else if(Train.BackgroundColour == clCallOnBackground)
22414  // use colour as also stopped at signal
22415  {
22416  CallOnWarning = true;
22417  }
22418  else if(Train.BackgroundColour == clSignalStopBackground)
22419  // use colour to distinguish from call-on
22420  {
22421  SignalStopWarning = true;
22422  }
22423  else if(Train.BackgroundColour == clBufferAttentionNeeded)
22424  // use colour to distinguish from ordinary buffer stop
22425  {
22426  BufferAttentionWarning = true;
22427  }
22428  }
22429  Utilities->CallLogPop(1796);
22430 }
22431 
22432 // ---------------------------------------------------------------------------
22433 
22435 {
22436  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CalcSignalStopLateness");
22437 
22438  // calculate lateness for running trains
22441  for(unsigned int x = 0; x < TrainVector.size(); x++)
22442  {
22443  TTrain &Train = TrainVectorAt(64, x);
22444  for(TActionVectorEntry * AVEntryPtr = &Train.TrainDataEntryPtr->ActionVector.front(); AVEntryPtr < &Train.TrainDataEntryPtr->ActionVector.back();
22445  AVEntryPtr++)
22446  {
22447  if(AVEntryPtr < Train.ActionVectorEntryPtr)
22448  {
22449  continue;
22450  }
22451  if((AVEntryPtr->ArrivalTime > TDateTime(-1)) && !Train.RevisedStoppedAtLoc() && (GetRepeatTime(42, AVEntryPtr->ArrivalTime, Train.RepeatNumber, Train.IncrementalMinutes) <
22452  TTClockTime))
22453  {
22454  OperatingTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(43, AVEntryPtr->ArrivalTime, Train.RepeatNumber, Train.IncrementalMinutes));
22456  }
22457 /* dropped departures after 2.7.0 because these don't count for 'failed to reach' numbers
22458  if((AVEntryPtr->DepartureTime > TDateTime(-1)) && (GetRepeatTime(44, AVEntryPtr->DepartureTime, Train.RepeatNumber, Train.IncrementalMinutes) <
22459  TTClockTime))
22460  {
22461  OperatingTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(45, AVEntryPtr->DepartureTime, Train.RepeatNumber, Train.IncrementalMinutes));
22462  OperatingTrainArrDep++;
22463  }
22464 */
22465  }
22466  }
22467 
22468  // calculate lateness for trains that haven't started yet (could be held awaiting entry)
22471 
22472  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
22473  {
22474  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
22475  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
22476  int IncrementalMinutes = 0;
22477  if(AVEntryLast.FormatType == Repeat)
22478  {
22479  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
22480  }
22481  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
22482  {
22483  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(y);
22484  if(TTOD.RunningEntry != NotStarted)
22485  {
22486  continue;
22487  }
22488  // note that can't rely on the above for sessionfiles saved before v0.6b as wasn't set to Running for Sns/Fsp/rsp & shuttles
22489  // but if trains had exited then would be set to Exited, so need to check against trains still operating - use the test below
22490  bool TrainOperatingFlag = false;
22491  for(unsigned int a = 0; a < TrainController->TrainVector.size(); a++)
22492  {
22493  if((TrainController->TrainVector.at(a).TrainDataEntryPtr == &TDEntry) && (TrainController->TrainVector.at(a).RepeatNumber == y))
22494  {
22495  TrainOperatingFlag = true;
22496  break;
22497  }
22498  }
22499  if(TrainOperatingFlag)
22500  {
22501  continue;
22502  }
22503  if(GetRepeatTime(46, TDEntry.ActionVector.at(0).EventTime, y, IncrementalMinutes) > TTClockTime)
22504  {
22505  break; // if the first time is greater than TTClockTime then all the rest will also be greater (& default of -1 will be less so will be ignored)
22506  }
22507  for(unsigned int z = 0; z < TDEntry.ActionVector.size(); z++)
22508  {
22509  TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
22510  if(GetRepeatTime(35, AVEntry.EventTime, y, IncrementalMinutes) > TTClockTime)
22511  {
22512  break; // all the rest will also be greater (& default of -1 will be less)
22513  }
22514  if(GetRepeatTime(36, AVEntry.ArrivalTime, y, IncrementalMinutes) > TTClockTime)
22515  {
22516  break; // all the rest will also be greater (& default of -1 will be less)
22517  }
22518  if(GetRepeatTime(37, AVEntry.DepartureTime, y, IncrementalMinutes) > TTClockTime)
22519  {
22520  break; // all the rest will also be greater (& default of -1 will be less)
22521  }
22522  if((AVEntry.ArrivalTime > TDateTime(-1)) && (GetRepeatTime(38, AVEntry.ArrivalTime, y, IncrementalMinutes) < TTClockTime))
22523  {
22524  NotStartedTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(39, AVEntry.ArrivalTime, y, IncrementalMinutes));
22526  }
22527 /* dropped departures after 2.7.0 as only interested in 'failed to reach' number - if train hasn't arrived then it hasn't departed so shouldn't count that as part of 'failed to reach'
22528  if((AVEntry.DepartureTime > TDateTime(-1)) && (GetRepeatTime(40, AVEntry.DepartureTime, y, IncrementalMinutes) < TTClockTime))
22529  {
22530  NotStartedTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(41, AVEntry.DepartureTime, y, IncrementalMinutes));
22531  NotStartedTrainArrDep++;
22532  }
22533 */
22534  }
22535  }
22536  }
22537  Utilities->CallLogPop(1894);
22538 }
22539 
22540 // ---------------------------------------------------------------------------
22541 
22543 // new v2.2.0 for OperatorActionPanel (OperatorActionPanel changed for ActionsDueForm at v2.13.0)
22544 // clears entries then adds values for running trains then for continuation entries
22545 // dont limit size here as need to check all trains (ActionsDueListBox is limited to 20 trains in Interface.cpp)
22546 {
22547  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RebuildOpTimeToActMultimap");
22548  OpTimeToActMultiMap.clear();
22549  TOpTimeToActMultiMapEntry OpTimeToActMultiMapEntry;
22550 
22551  if(!TrainVector.empty())
22552  // build OpTimeToActMultiMap entries for running trains
22553  {
22554  AnsiString HeadCode;
22555  // dropped in favour of TrainID for running trains int VecPos; //TrackVectorPosition of LeadElement or continuation where train is to enter
22556  int TrainID;
22557  THCandTrainPosParam HCandTrainPosParam;
22558  for(unsigned int x = 0; x < TrainVector.size(); x++)
22559  {
22560  HeadCode = TrainVectorAt(62, x).HeadCode;
22561  TrainID = TrainVectorAt(63, x).TrainID;
22562  HCandTrainPosParam.first = HeadCode;
22563  HCandTrainPosParam.second = TrainID;
22564  float TimeToAct = TrainVectorAt(65, x).OpTimeToAct;
22565  if((TimeToAct >= 0) && (TimeToAct < 59.9))
22566  // -1 indicates don't display
22567  {
22568  OpTimeToActMultiMapEntry.first = TimeToAct;
22569  OpTimeToActMultiMapEntry.second = HCandTrainPosParam;
22570  OpTimeToActMultiMap.insert(OpTimeToActMultiMapEntry);
22571  }
22572  }
22573  }
22574 /*
22575  * class TContinuationTrainExpectationEntry
22576  {
22577  public:
22578  AnsiString Description; ///< service description
22579  AnsiString HeadCode; ///< service headcode
22580  int RepeatNumber; ///< service RepeatNumber
22581  int IncrementalMinutes; ///< Repeat separation in minutes
22582  int IncrementalDigits; ///< Repeat headcode separation
22583  int VectorPosition; ///< TrackVectorPosition for the continuation element
22584  TTrainDataEntry *TrainDataEntryPtr; ///< points to the service entry in the timetable's TrainDataVector
22585  };
22586 
22587  Multimap class for TContinuationTrainExpectationEntry objects, where the access key is the expectation time
22588  typedef std::multimap<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMap;
22589  typedef TContinuationTrainExpectationMultiMap::iterator TContinuationTrainExpectationMultiMapIterator; ///< iterator for the multimap
22590  typedef std::pair<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMapPair; ///< a single multimap entry
22591 */
22592 
22594  // build OpTimeToActMultiMap entries for expected trains
22595  {
22596  // note that using the ContinuationTrainExpectationMultiMap automatically ensures that entries are in ascending time order
22597  // first have to calculate times to red signal for each train due to enter (ignore later trains as will likely change before they are due)
22598  float TimeToAct = 0; // minutes
22599  int DistanceToRedSignal = 0; // metres
22600  TContinuationEntryVecPosVector ContinuationEntryVecPosVector;
22601  // used to ensure only one train displayed for a given continuation
22602  ContinuationEntryVecPosVector.clear();
22603  bool LaterTrain = false;
22606  {
22607  LaterTrain = false;
22608  if(CTEIt->second.TrainDataEntryPtr->TrainOperatingDataVector.at(CTEIt->second.RepeatNumber).RunningEntry != NotStarted)
22609  {
22610  CTEIt++;
22611  continue; // not interested in running or exited trains
22612  }
22613  if(Track->TrackElementAt(934, CTEIt->second.VectorPosition).TrainIDOnElement > 0)
22614  {
22615  CTEIt++;
22616  continue;
22617  // don't include trains not entered yet when a train is already on the continuation
22618  }
22619  if(!ContinuationEntryVecPosVector.empty())
22620  {
22621  for(unsigned int x = 0; x < ContinuationEntryVecPosVector.size(); x++)
22622  {
22623  if(CTEIt->second.VectorPosition == ContinuationEntryVecPosVector.at(x))
22624  {
22625  LaterTrain = true;
22626  ;
22627  // skip past remaining trains waiting to enter at same point
22628  break;
22629  }
22630  }
22631  }
22632  if(LaterTrain)
22633  {
22634  CTEIt++;
22635  continue;
22636  }
22637  ContinuationEntryVecPosVector.push_back(CTEIt->second.VectorPosition);
22638  AnsiString HeadCode = CTEIt->second.HeadCode;
22639  float CurrentStopTime; // set to 0 at start of function
22640  float LaterStopTime; // set to 0 at start of function
22641  float RecoverableTime; // set to 0 at start of function
22642  int AvTrackSpeed; // set to 0 at start of function
22643  int TrainID = -1; // not yet allocated for train still to enter
22644  int DistanceToExit; //not used for continuation entries
22645  THVShortPair ExitPair;
22646  bool SigControlAndCanPassRedSignal = false; // doesn't apply for a continuation
22647 
22648 //at v2.11.0 found that with *AVPtr set to ...ActionVector.at(0) below instead of ...at(1) to stop signaller control trains throwing an error (because there is no ...at(1) -
22649 //discovered with Birmingham) the LaterStopTime isn't calculated and if a train does something other than depart after an arrival it is still listed in the actions due panel -
22650 //because it just calcs the distance to the red signal and converts that to a time. So here (after v2.11.0) this new test is introduced to determine whether a train is a
22651 //signaller control train (when the ActionVector size is 1) or not (ActionVector size > 1), and the ...at(value) is set accordingly - 0 for signaller control or 1 if not.
22652 
22653  int AtValue = 1;
22654  if(CTEIt->second.TrainDataEntryPtr->ActionVector.size() == 1)
22655  {
22656  AtValue = 0;
22657  }
22658  DistanceToRedSignal = CalcDistanceToRedSignalandStopTime(1, CTEIt->second.VectorPosition, 0,
22659  // EntryPos always 0 for entering at a continuation
22660  SigControlAndCanPassRedSignal, &CTEIt->second.TrainDataEntryPtr->ActionVector.at(AtValue), //see above note
22661  HeadCode, TrainID, CurrentStopTime, LaterStopTime, RecoverableTime, AvTrackSpeed, DistanceToExit, ExitPair);
22662  // for above VectorPosition is the first element to have its length included in the sum, so for a continuation it's the continuation itself
22663  // for a train it's the one in front of LeadElement
22664  if(AvTrackSpeed < 30)
22665  {
22666  AvTrackSpeed = 30;
22667  }
22668  if(DistanceToRedSignal == -1)
22669  {
22670  TimeToAct = 60.0;
22671  }
22672  else
22673  {
22674  int Speed = AvTrackSpeed;
22675  int MaxSpeed = int(CTEIt->second.TrainDataEntryPtr->MaxRunningSpeed);
22676  if(AvTrackSpeed > MaxSpeed)
22677  {
22678  Speed = MaxSpeed;
22679  }
22680  if(CTEIt->second.TrainDataEntryPtr->ActionVector.at(0).SignallerControl) //changed to ...at(0) from at(1) at v2.11.0 as SignallerControl only valid for ..at(0)
22681  // defined in timetable as under signaller control
22682  {
22683  Speed = CTEIt->second.TrainDataEntryPtr->SignallerSpeed;
22684  LaterStopTime = 0;
22685  }
22686  TimeToAct = LaterStopTime + DistanceToRedSignal * 3.6 / 60 / Speed;
22687  // accel & decel taken into account in
22688  // CalcDistanceToRedSignalandStopTime
22689  // 3.6 convertsKm/h to m/s & 60 converts seconds to minutes
22690  // don't need CurrentStopTime or RecoverableTime for continuation entries
22691  float MinsBefEnter = double(CTEIt->first - TTClockTime) * 86400.0 / 60.0;
22692  TimeToAct += MinsBefEnter;
22693  }
22694  THCandTrainPosParam HCandTrainPosParam;
22695  HCandTrainPosParam.first = HeadCode;
22696  HCandTrainPosParam.second = -1 - CTEIt->second.VectorPosition;
22697  // -1-CTE... because 2nd value covers TrainID if +ve &
22698  // continuation track vector position if -ve, -1 allows for vecpos being 0
22699  if(TimeToAct < 59.9) // if 60 don't enter a value in multimap
22700  {
22701  OpTimeToActMultiMapEntry.first = TimeToAct;
22702  OpTimeToActMultiMapEntry.second = HCandTrainPosParam;
22703  OpTimeToActMultiMap.insert(OpTimeToActMultiMapEntry);
22704  }
22705  CTEIt++;
22706  }
22707  }
22708  Utilities->CallLogPop(2081);
22709 }
22710 
22711 // ---------------------------------------------------------------------------
22712 
22714 // new for multiplayer
22715 // clears entries then adds values for running trains
22716 {
22717  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RebuildTimeToExitMultiMap");
22718  TimeToExitMultiMap.clear();
22719  TTimeToExitMultiMapEntry TimeToExitMultiMapEntry;
22720 
22721  if(!TrainVector.empty())
22722  // build map entries for running trains
22723  {
22724  TExitInfo ExitInfo; //corresponds to TServiceInfo in Interface
22725  THVShortPair ExitPair;
22726  float TimeToExit;
22727  for(unsigned int x = 0; x < TrainVector.size(); x++)
22728  {
22730  ExitInfo.RepeatNumber = short(TrainVectorAt(81, x).RepeatNumber);
22731  ExitInfo.TimeToExitSecs = short(TrainVectorAt(77, x).TimeToExit * 60);
22732  ExitPair = TrainVectorAt(76, x).ExitPair;
22733  if((ExitInfo.TimeToExitSecs >= 3570) || (ExitInfo.TimeToExitSecs < 1)) //59.5 mins or -60 secs
22734  {
22735  ExitInfo.TimeToExitSecs = -1;
22736  }
22737  TimeToExitMultiMapEntry.first = ExitPair;
22738  TimeToExitMultiMapEntry.second = ExitInfo;
22739  TimeToExitMultiMap.insert(TimeToExitMultiMapEntry);
22740  }
22741  }
22742  Utilities->CallLogPop(2323);
22743 }
22744 
22745 // ---------------------------------------------------------------------------
22746 
22747 int TTrainController::CalcDistanceToRedSignalandStopTime(int Caller, int TrackVectorPosition, int TrackVectorPositionEntryPos,
22748  bool SigControlAndCanPassRedSignal, TActionVectorEntry *AVPtr, AnsiString HeadCode, int TrainID, float &CurrentStopTime, float &LaterStopTime,
22749  float &RecoverableTime, int &AvTrackSpeed, int &DistanceToExit, THVShortPair &ExitPair)
22750 // new v2.2.0
22751 // vectorPosition is the value for the first element to be measured - for a continuation it's the continuation itself, for a train
22752 // it's the one after LeadElement, returns -1 for infinity - i.e. not approaching red signal (e.g. maybe cdt before reach it).
22753 // CurrentStopTime is the time to depart from the current station (if stopped at a station) and LaterStopTime is the total station
22754 // stop times for stations after the current one. DistanceToRedSignal is what the name implies, if -1 is returned the other values
22755 // aren't used - this means there is no display for the train in question
22756 {
22757  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",DistanceToRedSignal, " + AnsiString(TrackVectorPosition) + ", " +
22758  AnsiString(TrackVectorPositionEntryPos) + ", " + AVPtr->Command);
22759  int DistanceToRedSignal = 0;
22760  DistanceToExit = -1;
22761  ExitPair.first = -1;
22762  ExitPair.second = -1;
22763  int CumTrackSpeed = 0;
22764  // average track speed, in case need to use in time calc
22765  int TrackSpeedCount = 0;
22766  float KmPerLocationStop;
22767  float MaxAllowableSpeed;
22768 
22769  //below added at v2.6.1
22770  if(TrainID > -1) //will be -1 for trains not entered yet
22771  {
22772  TTrain &Train = TrainVectorAtIdent(51, TrainID);
22773  Train.DistanceToStationStop = 0; //if find a red signal first then this distance isn't needed
22774  Train.StationStopCalculated = false;
22775  }
22776  AvTrackSpeed = 0;
22777  int CurrentElement = TrackVectorPosition;
22778  int CurrentEntryPos = TrackVectorPositionEntryPos;
22779  int NextElement;
22780  int NextEntryPos;
22781  int NextExitPos;
22782 
22783  CurrentStopTime = 0;
22784  LaterStopTime = 0;
22785  RecoverableTime = 0;
22786  if(CurrentElement == -1) // train on end element, no action needed
22787  {
22788  Utilities->CallLogPop(2094);
22789  return(-1);
22790  }
22791  int CurrentExitPos;
22792 
22793  // get ExitPos for first element to be measured
22794  if(Track->TrackElementAt(935, CurrentElement).TrackType == Points)
22795  {
22796  if((CurrentEntryPos == 0) || (CurrentEntryPos == 2)) // leading point
22797  {
22798  if(Track->TrackElementAt(936, CurrentElement).Attribute == 0)
22799  {
22800  CurrentExitPos = 1;
22801  }
22802  else
22803  {
22804  CurrentExitPos = 3;
22805  }
22806  }
22807  else
22808  {
22809  CurrentExitPos = 0; // trailing point
22810  }
22811  }
22812  else
22813  {
22814  CurrentExitPos = Track->GetNonPointsOppositeLinkPos(CurrentEntryPos);
22815  }
22816  // get CumTrackSpeed for first measured element
22817 
22818  TConfiguration CurrentExitConfig = Track->TrackElementAt(937, CurrentElement).Config[CurrentExitPos];
22819  int CurrentAttribute = Track->TrackElementAt(938, CurrentElement).Attribute;
22820  bool CurrentElementFailed = Track->TrackElementAt(1549, CurrentElement).Failed; //added at v2.13.2
22821 
22822  // check if currently stopped at a location, and if so add the remaining dwell time
22823  // can't use CurrentElement as that is in front of LeadElement and might not be at the location
22824  if(TrainID > -1)
22825  // -1 for a continuation and can't be at a location as not yet entered
22826  {
22827  TTrain &Train = TrainVectorAtIdent(39, TrainID); //Train wasn't a reference before v2.6.1 mods so FirstLaterStopRecoverableTime wouldn't be reset for the referenced train
22829  // this used to deduct from RecoverableTime when arrive at a location
22830  if(Train.RevisedStoppedAtLoc())
22831  {
22832  if(Train.StoppedForTrainInFront || Train.TrainInFront)
22833  {
22834  Utilities->CallLogPop(2082);
22835  return(-1); // no action needed
22836  }
22838  { //added '|| (Train.ActionVectorEntryPtr->FormatType == TimeCmdDescription)' at v2.16.1 so description ignored in calculating action due time
22839  //added TimeCmdMaxSpeed at v2.21.0
22840  Utilities->CallLogPop(2083);
22841  return(-1); // not due a departure, description change or a max speed change so no action needed
22842  }
22843  else if(((Train.ActionVectorEntryPtr + 1)->FormatType == TimeLoc) && ((Train.ActionVectorEntryPtr->FormatType == TimeCmdDescription) || Train.ActionVectorEntryPtr->FormatType == TimeCmdMaxSpeed)) // due a departure immediately after change of description or change of max speed
22844  { //added at v2.16.1 to cover description change due next then a departure //added TimeCmsMaxSpeed at v2.21.0
22845  double TimeToDepart = double((Train.GetTrainTime(68, (Train.ActionVectorEntryPtr + 1)->DepartureTime)) - TrainController->TTClockTime) * 86400 / 60; // mins to depart excluding possible 30sec allowance from LastActionTime
22846  //need repeat time for the above
22847  if((Train.ActionVectorEntryPtr + 1)->DepartureTime == Train.ActionVectorEntryPtr->EventTime) //don't need repeat time here
22848  {
22849  TimeToDepart+= 0.5; //add in the 30 secs if depature time same as description or max speed change time
22850  }
22851  if(TimeToDepart < 0.5)
22852  {
22853  TimeToDepart = 0.5;
22854  }
22855  // can't convert a TDateTime to a float directly
22856  CurrentStopTime = float(TimeToDepart);
22857  AVPtr++;
22858  AVPtr++;
22859  }
22860  else if((Train.ActionVectorEntryPtr->FormatType == TimeLoc) || (Train.ActionVectorEntryPtr->FormatType == TimeTimeLoc)) // due a departure as next action
22861  {
22862  double TimeToDepart = double(Train.ReleaseTime - TrainController->TTClockTime) * 86400 / 60; // mins to depart
22863  // can't convert a TDateTime to a float directly
22864  CurrentStopTime = float(TimeToDepart);
22865  AVPtr++;
22866  }
22867  else //added at v2.16.1 to catch all other combinations
22868  { //none of the above so no action needed
22869  Utilities->CallLogPop(2628);
22870  return(-1);
22871  }
22872  }
22873  }
22874  // check if CurrentElement is a red signal, but ok if autosig route after provided signal not failed
22875  if((CurrentExitConfig == Signal) && (CurrentAttribute == 0))
22876  // ok if autosig route after red signal unless signal has failed
22877  {
22878  int NextElement = Track->TrackElementAt(939, CurrentElement).Conn[CurrentExitPos];
22879  int NextEntryPos = Track->TrackElementAt(940, CurrentElement).ConnLinkPos[CurrentExitPos];
22880  int RouteNumber; // holder for referenced value, not used
22881  if((AllRoutes->GetRouteTypeAndNumber(33, NextElement, NextEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !CurrentElementFailed)
22882  { //CurrentElementFailed added at v2.13.2
22883  Utilities->CallLogPop(2078);
22884  return(-1);
22885  }
22886  else if(SigControlAndCanPassRedSignal)
22887  // ignore signal and increment CurrentElement to NextElement
22888  {
22889  if(Track->TrackElementAt(941, NextElement).TrackType == Points)
22890  {
22891  if((NextEntryPos == 0) || (NextEntryPos == 2))
22892  // leading entry point
22893  {
22894  if(Track->TrackElementAt(942, NextElement).Attribute == 0)
22895  {
22896  NextExitPos = 1;
22897  }
22898  else
22899  {
22900  NextExitPos = 3;
22901  }
22902  }
22903  else
22904  {
22905  NextExitPos = 0; // trailing entry point
22906  }
22907  }
22908  else
22909  {
22910  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
22911  }
22912  CurrentElement = NextElement;
22913  CurrentEntryPos = NextEntryPos;
22914  CurrentExitPos = NextExitPos;
22915  CurrentExitConfig = Track->TrackElementAt(943, CurrentElement).Config[CurrentExitPos];
22916  CurrentAttribute = Track->TrackElementAt(944, CurrentElement).Attribute;
22917  }
22918  else if((TrainID > -1) && (TrainVectorAtIdent(40, TrainID).TrainMode == Timetable)) // ignore signallercontrol or will
22919  // give 'NOW' indication after allowed to pass stop signal when LeadMidLag (AllowedToPassRedSignal reset by this point)
22920  {
22921  Utilities->CallLogPop(2084);
22922  return(0);
22923  // stopped with red signal in front, don't need AvSpeedLimit in this case, & if at location awaiting departure dwell time already calculated
22924  }
22925  }
22926  int LaterStopNumber = 0;
22927  int x = 0;
22928  // added in v2.4.0 to prevent endless circling round track loops - spotted by Xeon 09/03/20 & reported by emsil
22929 
22930  while(!((CurrentExitConfig == Signal) && (CurrentAttribute == 0)))
22931  // not red signal next (in fwd direction) so enter loop to calc CumLength
22932  {
22933  x++; // added in v2.4.0 as above
22934  if(x > 5000)
22935  {
22936  Utilities->CallLogPop(2120);
22937  return(-1);
22938  }
22939  if(CurrentEntryPos > 1)
22940  {
22941  DistanceToRedSignal += Track->TrackElementAt(916, CurrentElement).Length23;
22942  CumTrackSpeed += Track->TrackElementAt(945, CurrentElement).SpeedLimit23;
22943  }
22944  else
22945  {
22946  DistanceToRedSignal += Track->TrackElementAt(917, CurrentElement).Length01;
22947  CumTrackSpeed += Track->TrackElementAt(946, CurrentElement).SpeedLimit01;
22948  }
22949  TrackSpeedCount++;
22950 
22951  //added for multiplayer - exiting at a continuation and continuation length already added
22952  if((Track->TrackElementAt(1407, CurrentElement).TrackType == Continuation) && (Track->TrackElementAt(1408, CurrentElement).Config[CurrentExitPos] == End))
22953  {
22954  DistanceToExit = DistanceToRedSignal; //don't need to exit function here as will exit when find that the next Conn value is -1
22955  ExitPair.first = Track->TrackElementAt(1409, CurrentElement).HLoc;
22956  ExitPair.second = Track->TrackElementAt(1410, CurrentElement).VLoc;
22957  //here repeat calcs for MaxAllowableSpeed & AvTrackSpeed as done at end for stop signal
22958  //need here as next element will be -1 so will exit before calcs at end
22959  if(TrackSpeedCount > 0)
22960  {
22961  MaxAllowableSpeed = CumTrackSpeed / TrackSpeedCount;
22962  }
22963  else // shouldn't reach here but include to prevent divide by zero error
22964  {
22965  if(CurrentEntryPos > 1)
22966  {
22967  MaxAllowableSpeed = Track->TrackElementAt(951, CurrentElement).SpeedLimit23;
22968  }
22969  else
22970  {
22971  MaxAllowableSpeed = Track->TrackElementAt(952, CurrentElement).SpeedLimit01;
22972  }
22973  // Train MaxRunningSpeed taken into account in RebuildOpTimeToActMultimap
22974  }
22975  //calc AvTrackSpeed
22976  if(LaterStopNumber > 0)
22977  {
22978  KmPerLocationStop = float(DistanceToRedSignal) / LaterStopNumber / 1000; // m to km
22979  AvTrackSpeed = (8.75 * KmPerLocationStop) + 44;
22980  // Av speed calculation based on formula: Speed = 8.75*(kms/location stop) + 44 km/sec (from experiments), subject to a maximum of
22981  // average line speed/2 (for half distance accelerating and half decelerating.
22982  }
22983  else
22984  {
22985  AvTrackSpeed = (sqrt(float(DistanceToRedSignal) / 1000) * 44) + 60;
22986  // using linear trendline for accel & decel distance at various speeds
22987  // at half braking, speed never < 60 using this
22988  }
22989  if(AvTrackSpeed > MaxAllowableSpeed)
22990  {
22991  AvTrackSpeed = MaxAllowableSpeed;
22992  }
22993  }
22994 
22995  // added at v2.6.1 to find DistanceToStationStop for trains running early
22996  if(TrainID > -1) //can ignore continuation entries as these don't run early
22997  {
22998  TTrain &Train = TrainVectorAtIdent(52, TrainID);
22999  if(!Train.StationStopCalculated)
23000  {
23001  if(Train.TrainMode == Timetable)
23002  {
23003  bool StopRequired = false;
23004  if(!Train.TimetableFinished && (Train.NameInTimetableBeforeCDT(16, Track->TrackElementAt(1005, CurrentElement).ActiveTrackElementName,
23005  StopRequired) > -1) && ((Track->TrackElementAt(1006, CurrentElement).StationEntryStopLinkPos1 == CurrentEntryPos) ||
23006  (Track->TrackElementAt(1010, CurrentElement).StationEntryStopLinkPos2 == CurrentEntryPos) ||
23007  (Track->TrackElementAt(1654, CurrentElement).StationEntryStopLinkPos3 == CurrentEntryPos) ||
23008  (Track->TrackElementAt(1655, CurrentElement).StationEntryStopLinkPos4 == CurrentEntryPos)))
23009  {
23010  // no need to add in the length of element to CumulativeLength
23011  if(StopRequired)
23012  {
23013  Train.DistanceToStationStop = DistanceToRedSignal; // DistanceToRedSignal holds the intermediate distance to this point
23014  Train.StationStopCalculated = true; //don't want to update it with later stops
23015  }
23016  }
23017  }
23018  }
23019  }
23020  // check for train in front, but if on a bridge on other track then ok
23021  TTrackElement TE = Track->TrackElementAt(947, CurrentElement);
23022  int TrainOnElement;
23023  if(TE.TrackType != Bridge)
23024  {
23025  TrainOnElement = TE.TrainIDOnElement;
23026  }
23027  else
23028  {
23029  if(CurrentEntryPos > 1)
23030  {
23032  }
23033  else
23034  {
23036  }
23037  }
23038  if((TrainOnElement > -1) && (TrainOnElement != TrainID))
23039  // train in front before red signal
23040  {
23041  Utilities->CallLogPop(2085);
23042  return(-1);
23043  }
23044  // add to stoptime if required
23045  if(Track->TrackElementAt(948, CurrentElement).ActiveTrackElementName != "")
23046  {
23047  double StopTimeDouble;
23048  while(AVPtr->FormatType == PassTime)
23049  {
23050  AVPtr++; // skip past any passes
23051  }
23052  if((Track->TrackElementAt(949, CurrentElement).ActiveTrackElementName == AVPtr->LocationName) && ((AVPtr->FormatType == TimeLoc) ||
23053  (AVPtr->FormatType == TimeTimeLoc)))
23054  // stop due here so calc dwell time & advance Ptr
23055  {
23056  if(AVPtr->FormatType == TimeTimeLoc)
23057  {
23058  LaterStopNumber++;
23059  StopTimeDouble = double(AVPtr->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0;
23060  if(StopTimeDouble < 0.5)
23061  {
23062  StopTimeDouble = 0.5;
23063  }
23064  // at least 30 secs delay at station
23065  // can't convert a TDateTime to a float directly
23066  LaterStopTime += float(StopTimeDouble);
23067  RecoverableTime += StopTimeDouble - 0.5;
23068  if((LaterStopNumber == 1) && (TrainID > -1))
23069  {
23070  TrainVectorAtIdent(41, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
23071  }
23072  AVPtr++;
23073  }
23074  else if((AVPtr->FormatType == TimeLoc) && (AVPtr->ArrivalTime != TDateTime(-1))) // must be an arrival
23075  {
23076  if((AVPtr + 1)->FormatType == TimeLoc)
23077  // must be a departure
23078  {
23079  LaterStopNumber++;
23080  StopTimeDouble = double((AVPtr + 1)->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0; //diff will be same for all repeats
23081  // can't convert a TDateTime to a float directly //so repeat times not required
23082  if(TrainID > -1) //exclude trains still to enter
23083  {
23084  TTrain &Train = TrainVectorAtIdent(67, TrainID);
23085  if(TTClockTime > Train.GetTrainTime(69, AVPtr->ArrivalTime)) //running late, added at v2.16.1
23086  {
23087  StopTimeDouble = double(Train.GetTrainTime(70, (AVPtr + 1)->DepartureTime) - TTClockTime) * 86400.0 / 60.0; //may be -ve but if so it's set to 0.5 later
23088  // can't convert a TDateTime to a float directly
23089  }
23090  }
23091  if(StopTimeDouble < 0.5)
23092  {
23093  StopTimeDouble = 0.5;
23094  }
23095  // at least 30 secs delay at station
23096  LaterStopTime += float(StopTimeDouble);
23097  RecoverableTime += StopTimeDouble - 0.5;
23098  if((LaterStopNumber == 1) && (TrainID > -1))
23099  {
23100  TrainVectorAtIdent(42, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
23101  }
23102  AVPtr++;
23103  AVPtr++;
23104  }
23105  else if((((AVPtr + 1)->FormatType == TimeCmdDescription) || ((AVPtr + 1)->FormatType == TimeCmdMaxSpeed)) && ((AVPtr + 2)->FormatType == TimeLoc)) //change of description or max speed then departure
23106  { //added at v2.16.1 so description changes ignored in calculating time to act //added TimeCmdMaxSpeed at v2.21.0
23107  LaterStopNumber++;
23108  StopTimeDouble = double((AVPtr + 2)->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0; //diff will be same for all repeats
23109  // can't convert a TDateTime to a float directly //so repeat times not required
23110  if(TrainID > -1) //exclude trains still to enter
23111  {
23112  TTrain &Train = TrainVectorAtIdent(68, TrainID);
23113  if(TTClockTime > Train.GetTrainTime(71, AVPtr->ArrivalTime)) //running late, added at v2.16.1
23114  {
23115  StopTimeDouble = double(Train.GetTrainTime(72, (AVPtr + 2)->DepartureTime) - TTClockTime) * 86400.0 / 60.0; //may be -ve but if so it's set to 0.5 later
23116  // can't convert a TDateTime to a float directly
23117  }
23118  }
23119  if(StopTimeDouble < 0.5)
23120  {
23121  StopTimeDouble = 0.5;
23122  }
23123  // at least 30 secs delay at station
23124  LaterStopTime += float(StopTimeDouble);
23125  RecoverableTime += StopTimeDouble - 0.5;
23126  if((LaterStopNumber == 1) && (TrainID > -1))
23127  {
23128  TrainVectorAtIdent(69, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
23129  }
23130  AVPtr++;
23131  AVPtr++;
23132  AVPtr++;
23133  }
23134  else // does something else at the location so no calculation needed
23135  {
23136  Utilities->CallLogPop(2086);
23137  return(-1);
23138  }
23139  }
23140  }
23141  }
23142  NextElement = Track->TrackElementAt(950, CurrentElement).Conn[CurrentExitPos];
23143  if(NextElement == -1) // reached end element, no action needed
23144  {
23145  Utilities->CallLogPop(2077);
23146  return(-1);
23147  }
23148  NextEntryPos = Track->TrackElementAt(919, CurrentElement).ConnLinkPos[CurrentExitPos];
23149  // get NextExitPos
23150  if(Track->TrackElementAt(920, NextElement).TrackType == Points)
23151  {
23152  if((NextEntryPos == 0) || (NextEntryPos == 2))
23153  // leading entry point
23154  {
23155  if(Track->TrackElementAt(921, NextElement).Attribute == 0)
23156  {
23157  NextExitPos = 1;
23158  }
23159  else
23160  {
23161  NextExitPos = 3;
23162  }
23163  }
23164  else
23165  {
23166  NextExitPos = 0; // trailing entry point
23167  }
23168  }
23169  else
23170  {
23171  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
23172  }
23173  CurrentElement = NextElement;
23174  CurrentEntryPos = NextEntryPos;
23175  CurrentExitPos = NextExitPos;
23176  CurrentExitConfig = Track->TrackElementAt(922, CurrentElement).Config[CurrentExitPos];
23177  CurrentAttribute = Track->TrackElementAt(923, CurrentElement).Attribute;
23178  CurrentElementFailed = Track->TrackElementAt(1550, CurrentElement).Failed; //added at v2.13.2
23179  }
23180  if((CurrentExitConfig == Signal) && (CurrentAttribute == 0))
23181  // ok if autosig route after red signal, no action needed
23182  {
23183  int NextElement = Track->TrackElementAt(924, CurrentElement).Conn[CurrentExitPos];
23184  int NextEntryPos = Track->TrackElementAt(925, CurrentElement).ConnLinkPos[CurrentExitPos];
23185  int RouteNumber; // holder for referenced value, not used
23186  if((AllRoutes->GetRouteTypeAndNumber(31, NextElement, NextEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !CurrentElementFailed)
23187  { //CurrentElementFailed added at v2.13.2
23188  Utilities->CallLogPop(2095);
23189  return(-1);
23190  }
23191  }
23192 
23193  if(TrackSpeedCount > 0)
23194  {
23195  MaxAllowableSpeed = CumTrackSpeed / TrackSpeedCount;
23196  }
23197  else // shouldn't reach here but include to prevent divide by zero error
23198  {
23199  if(CurrentEntryPos > 1)
23200  {
23201  MaxAllowableSpeed = Track->TrackElementAt(1433, CurrentElement).SpeedLimit23;
23202  }
23203  else
23204  {
23205  MaxAllowableSpeed = Track->TrackElementAt(1434, CurrentElement).SpeedLimit01;
23206  }
23207  // Train MaxRunningSpeed taken into account in RebuildOpTimeToActMultimap
23208  }
23209 
23210  if(LaterStopNumber > 0)
23211  {
23212  KmPerLocationStop = float(DistanceToRedSignal) / LaterStopNumber / 1000; // m to km
23213  AvTrackSpeed = (8.75 * KmPerLocationStop) + 44;
23214  }
23215  else
23216  // Av speed calculation based on formula: Speed = 8.75*(kms/location stop) + 44 km/sec (from experiments), subject to a maximum of
23217  // average line speed/2 (for half distance accelerating and half decelerating.
23218  {
23219  AvTrackSpeed = (sqrt(float(DistanceToRedSignal) / 1000) * 44) + 60;
23220  // using linear trendline for accel & decel distance at various speeds
23221  // at half braking, speed never < 60 using this
23222  }
23223  if(AvTrackSpeed > MaxAllowableSpeed)
23224  {
23225  AvTrackSpeed = MaxAllowableSpeed;
23226  }
23227  Utilities->CallLogPop(2096);
23228  return(DistanceToRedSignal);
23229 }
23230 
23231 // ---------------------------------------------------------------------------
23232 // end of TTrainController entries
23233 // ---------------------------------------------------------------------------
TTrain::LinkOccupied
bool LinkOccupied(int Caller, int TrackVectorPosition, int LinkNumber)
Added at v1.2.0: true if any part of train on specific link, false otherwise, including no link prese...
Definition: TrainUnit.cpp:9743
TAllRoutes::TrackIsInARoute
bool TrackIsInARoute(int Caller, int TrackVectorPosition, int LinkPos)
Examines Route2MultiMap and if the element at TrackVectorPosition with LinkPos (can be entry or exit)...
Definition: TrackUnit.cpp:19922
TActionVectorEntry::EventTime
TDateTime EventTime
Definition: TrainUnit.h:139
TTrain::AllowedToPassRedSignal
bool AllowedToPassRedSignal
set when train has been called on, or when under signaller control and instructed to pass a red signa...
Definition: TrainUnit.h:393
JoinedByOther
@ JoinedByOther
Definition: TrainUnit.h:55
TTrainController::CheckShuttleRepeatTime
bool CheckShuttleRepeatTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes)
Check that shuttle link services have consistent times, true for success.
Definition: TrainUnit.cpp:16891
TTrain::ZeroPowerNoNewShuttleFromNonRepeatMessage
bool ZeroPowerNoNewShuttleFromNonRepeatMessage
Definition: TrainUnit.h:359
TTrain::ZeroPowerNoNewServiceMessage
bool ZeroPowerNoNewServiceMessage
Definition: TrainUnit.h:358
TTrain::VOffset
int VOffset[4]
each headcode character is an 8x8 pixel graphic and must be placed within a 16x16 pixel element,...
Definition: TrainUnit.h:518
TTrainController
Handles all train and timetable activities, only one object created.
Definition: TrainUnit.h:750
TTrain::TTrain
TTrain(int Caller, int RearStartElementIn, int RearStartExitPosIn, AnsiString InputCode, int StartSpeed, int Mass, double MaxRunningSpeed, double MaxBrakeRate, double PowerAtRail, TTrainMode TrainMode, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits, int SignallerMaxSpeed)
Constructor, sets listed member values.
Definition: TrainUnit.cpp:73
TAllRoutes::LockedRouteVector
TLockedRouteVector LockedRouteVector
the vector that stores all the locked routes on the railway
Definition: TrackUnit.h:1746
TRailGraphics::smOrange
Graphics::TBitmap * smOrange
Definition: GraphicUnit.h:904
TActionVector
std::vector< TActionVectorEntry > TActionVector
contains all actions for a single train
Definition: TrainUnit.h:182
TUtilities::LoadFileString
AnsiString LoadFileString(std::ifstream &InFile)
loads a string value from the file
Definition: Utilities.cpp:214
TTrainController::CheckStartPositionValidity
bool CheckStartPositionValidity(int Caller, AnsiString RearElementStr, AnsiString FrontElementStr, bool GiveMessages)
A timetable validation function where train starting positions are checked for validity,...
Definition: TrainUnit.cpp:16553
TTrainDataEntry::FixedDescription
AnsiString FixedDescription
Definition: TrainUnit.h:217
TTrain::ChangeTrainDirection
void ChangeTrainDirection(int Caller, bool NoLogFlag)
Reverses the direction of motion of the train.
Definition: TrainUnit.cpp:6662
TActionVectorEntry::SplitDistribution
AnsiString SplitDistribution
Definition: TrainUnit.h:125
TTrain::MidEntryPos
int MidEntryPos
Definition: TrainUnit.h:381
TTrainController::RebuildOpTimeToActMultimap
void RebuildOpTimeToActMultimap(int Caller)
new v2.2.0 for OperatorActionPanel (OperatorActionPanel changed for ActionsDueForm at v2....
Definition: TrainUnit.cpp:22542
TTrainController::PwrHigh
bool PwrHigh
Definition: TrainUnit.h:860
TTrainController::BuildContinuationTrainExpectationMultiMap
void BuildContinuationTrainExpectationMultiMap(int Caller)
populate the ContinuationTrainExpectationMultiMap during timetable loading
Definition: TrainUnit.cpp:18018
TFixedTrackPiece::GraphicPtr
Graphics::TBitmap * GraphicPtr
the track bitmap for display on the zoomed-in railway
Definition: TrackUnit.h:92
SignallerMoveForwards
@ SignallerMoveForwards
Definition: TrainUnit.h:56
TRailGraphics::CodeR
Graphics::TBitmap * CodeR
Definition: GraphicUnit.h:1017
Create
@ Create
Definition: TrainUnit.h:55
TTrainController::TContinuationEntryVecPosVector
std::vector< int > TContinuationEntryVecPosVector
ensures only one train displayed for a given continuation
Definition: TrainUnit.h:837
TAllRoutes::SetAllRearwardsSignals
void SetAllRearwardsSignals(int Caller, int Attribute, int RouteNumber, int RouteStartPosition)
Set rearwards signals from the specified route starting position.
Definition: TrackUnit.cpp:20928
Arrive
@ Arrive
Definition: TrainUnit.h:55
ChangeDirection
@ ChangeDirection
Definition: TrainUnit.h:55
TTrain::ZeroPowerNoCDTMessage
bool ZeroPowerNoCDTMessage
Definition: TrainUnit.h:357
TTrain::CallingOnFlag
bool CallingOnFlag
calling on permitted
Definition: TrainUnit.h:399
Depart
@ Depart
Definition: TrainUnit.h:55
TRailGraphics::gl89set
Graphics::TBitmap * gl89set
Definition: GraphicUnit.h:721
TTrainController::CheckHeadCodeValidity
bool CheckHeadCodeValidity(int Caller, bool GiveMessages, AnsiString HeadCode)
Returns true if the headcode complies with requirements.
Definition: TrainUnit.cpp:13087
TRailGraphics::gl88set
Graphics::TBitmap * gl88set
Definition: GraphicUnit.h:719
PerfLogForm
TPerfLogForm * PerfLogForm
Definition: PerfLogUnit.cpp:11
clBufferStopBackground
#define clBufferStopBackground
Definition: GraphicUnit.h:292
TTrain::MaxRunningSpeed
double MaxRunningSpeed
the current maximum train running speed
Definition: TrainUnit.h:445
TTrack::BarriersDownVector
TActiveLCVector BarriersDownVector
vector of LCs with barriers down
Definition: TrackUnit.h:805
ChangeMaxSpeed
@ ChangeMaxSpeed
Definition: TrainUnit.h:58
TPrefDirElement::GetXLinkPos
int GetXLinkPos() const
Returns the XLink array position.
Definition: TrackUnit.h:287
TTrack::IsLCBarrierDownAtHV
bool IsLCBarrierDownAtHV(int Caller, int HLoc, int VLoc)
True if an open (to trains) level crossing is found at H & V.
Definition: TrackUnit.cpp:7512
RestoreTimetableControl
@ RestoreTimetableControl
Definition: TrainUnit.h:56
FailCrashed
@ FailCrashed
Definition: TrainUnit.h:42
TRailGraphics::TempHeadCode
Graphics::TBitmap * TempHeadCode
Definition: GraphicUnit.h:910
TAllRoutes::AutoSigsRoute
@ AutoSigsRoute
Definition: TrackUnit.h:1675
IntermediateSequence
@ IntermediateSequence
Definition: TrainUnit.h:81
TTrack::TSigElement::Attribute
int Attribute
the signal state - red, yellow, double yellow or green
Definition: TrackUnit.h:730
TActionVectorEntry::LocationType
TTimetableLocationType LocationType
indicates where the train is when the relevant action occurs
Definition: TrainUnit.h:145
TTrack::GapFlashGreenPosition
int GapFlashGreenPosition
Definition: TrackUnit.h:787
TAllRoutes::TRouteElementPair
std::pair< int, unsigned int > TRouteElementPair
defines a specific element in a route, the first (int) value is the vector position in the AllRoutesV...
Definition: TrackUnit.h:1687
NamedNonStationLocation
@ NamedNonStationLocation
Definition: TrackUnit.h:67
FinRemHere
@ FinRemHere
Definition: TrainUnit.h:70
TTrain::HeadCodeGrPtr
Graphics::TBitmap * HeadCodeGrPtr[4]
points to the headcode segment graphics e.g. 5,A,4,7.
Definition: TrainUnit.h:535
TTrainController::TContinuationTrainExpectationEntry::TrainDataEntryPtr
TTrainDataEntry * TrainDataEntryPtr
points to the service entry in the timetable's TrainDataVector
Definition: TrainUnit.h:798
FSHNewService
@ FSHNewService
Definition: TrainUnit.h:71
clB3G5R5
#define clB3G5R5
Definition: GraphicUnit.h:203
TTrain::CoastingBrakeRate
double CoastingBrakeRate
the train brake rate when coasting
Definition: TrainUnit.h:453
TTrain::MaxBrakeRate
double MaxBrakeRate
the maximum brake rate that the train can achieve
Definition: TrainUnit.h:449
TTimetableLocationType
TTimetableLocationType
Definition: TrainUnit.h:75
TAllRoutes::DiagonalFouledByRoute
bool DiagonalFouledByRoute(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber)
As above but only checks for a route (may or may not be a train present (new at v1....
Definition: TrackUnit.cpp:21689
TTrainController::CheckForDuplicateCrossReferences
bool CheckForDuplicateCrossReferences(int Caller, AnsiString MainHeadCode, AnsiString SecondHeadCode, bool GiveMessages)
A timetable validation function where referenced services are checked for uniqueness,...
Definition: TrainUnit.cpp:15786
TTrackElement::StationEntryStopLinkPos2
int StationEntryStopLinkPos2
Definition: TrackUnit.h:153
TTrain::MidExitPos
int MidExitPos
Definition: TrainUnit.h:381
TRailGraphics::CodeD
Graphics::TBitmap * CodeD
Definition: GraphicUnit.h:1003
TTrain::TrainFailed
bool TrainFailed
Definition: TrainUnit.h:429
TUtilities::CheckFileStringZeroDelimiter
bool CheckFileStringZeroDelimiter(std::ifstream &InFile)
checks that the value is a string ('0' only accepted as the delimiter), returns true for success
Definition: Utilities.cpp:459
TTrack::TTrackVectorIterator
std::vector< TTrackElement >::iterator TTrackVectorIterator
iterator for TTrackVector
Definition: TrackUnit.h:652
TRailGraphics::Code_w
Graphics::TBitmap * Code_w
Definition: GraphicUnit.h:986
FailDerailed
@ FailDerailed
Definition: TrainUnit.h:42
TTrainController::SPADWarning
bool SPADWarning
Definition: TrainUnit.h:846
TTrain::ZeroPowerNoFrontSplitMessage
bool ZeroPowerNoFrontSplitMessage
Definition: TrainUnit.h:353
TRailGraphics::Code_s
Graphics::TBitmap * Code_s
Definition: GraphicUnit.h:982
TTrainController::GetExitLocationAndAt
AnsiString GetExitLocationAndAt(int Caller, TNumList &ExitList, AnsiString &AllowedExits) const
Check all timetable names in ExitList, if all same return " at [name]" + AllowableExits = elements,...
Definition: TrainUnit.cpp:21830
TTrain::SaveOneSessionTrain
void SaveOneSessionTrain(int Caller, std::ofstream &OutFile)
Data for a single train is saved to a session file.
Definition: TrainUnit.cpp:8614
TTrain::DepartureTimeSet
bool DepartureTimeSet
set when stopped at a location and the next action is departure (set in UpdateTrain when ReleaseTime ...
Definition: TrainUnit.h:401
TTrain::Plotted
bool Plotted
set when train plotted on screen
Definition: TrainUnit.h:507
TTrain::LagElement
int LagElement
Definition: TrainUnit.h:381
TTrain::RemainHere
void RemainHere(int Caller)
Sends the 'train terminated' message to the performance log and sets TimetableFinished to true.
Definition: TrainUnit.cpp:6800
TTrackElement::StationEntryStopLinkPos3
int StationEntryStopLinkPos3
Definition: TrackUnit.h:153
TTrainController::BufferAttentionWarning
bool BufferAttentionWarning
Definition: TrainUnit.h:846
TTrainController::TrainVectorAtIdent
TTrain & TrainVectorAtIdent(int Caller, int TrainID)
Return a reference to the train with ID TrainID, carries out validity checking on TrainID.
Definition: TrainUnit.cpp:11060
TPrefDirElement::GetRouteEXGraphicPtr
Graphics::TBitmap * GetRouteEXGraphicPtr()
Returns route graphic.
Definition: TrackUnit.h:323
TRailGraphics::smLightBlue
Graphics::TBitmap * smLightBlue
Definition: GraphicUnit.h:901
TTrainController::TContinuationAutoSigEntry::RouteNumber
int RouteNumber
the AllRoutesVector position of the route
Definition: TrainUnit.h:772
TTrainController::StopTTClockMessage
void StopTTClockMessage(int Caller, AnsiString Message)
sends a message to the user and stops the timetable clock while it is displayed
Definition: TrainUnit.cpp:17749
TExitInfo::TimeToExitSecs
short TimeToExitSecs
Definition: TrainUnit.h:111
TTrainController::OperatingTrainLateArr
int OperatingTrainLateArr
< all these set to 0 in constructor
Definition: TrainUnit.h:906
TRailGraphics::CodeJ
Graphics::TBitmap * CodeJ
Definition: GraphicUnit.h:1009
TUtilities::IncrementAnsiTimeOneMinute
AnsiString IncrementAnsiTimeOneMinute(AnsiString TimeVal)
takes "HH:MM" and increments it to "HH:MX", where MX == MM + 1, incrementing the hour if necessary
Definition: Utilities.cpp:850
TRailGraphics::CodeQ
Graphics::TBitmap * CodeQ
Definition: GraphicUnit.h:1016
TDisplay::GetOutputLog9
TLabel * GetOutputLog9()
Definition: DisplayUnit.h:185
TAllRoutes::RemoveRouteElement
void RemoveRouteElement(int Caller, int HLoc, int VLoc, int ELink)
Erases the route element from Route2MultiMap and from the PrefDirVector.
Definition: TrackUnit.cpp:20717
TTrainController::CrashWarning
bool CrashWarning
Definition: TrainUnit.h:846
TTrainController::CheckLocationValidity
bool CheckLocationValidity(int Caller, AnsiString LocStr, bool GiveMessages, bool CheckLocationsExistInRailway)
Returns true if the location name complies with requirements.
Definition: TrainUnit.cpp:13037
TAllRoutes::TLockedRouteClass::LastXLinkPos
int LastXLinkPos
the XLinkPos value of the last (i.e. most forward) element in the route
Definition: TrackUnit.h:1667
TTrain::PlotTrain
void PlotTrain(int Caller, TDisplay *Disp)
Plots the train on the display in normal (zoomed-in) mode.
Definition: TrainUnit.cpp:9710
TNumListIterator
TNumList::iterator TNumListIterator
Definition: TrainUnit.h:99
FailLevelCrossingCrash
@ FailLevelCrossingCrash
Definition: TrainUnit.h:44
TActionVectorEntry::DepartureTime
TDateTime DepartureTime
relevant times at which the action is timetabled, zeroed on creation so change to -1 as a marker for ...
Definition: TrainUnit.h:139
TRailGraphics::Code_f
Graphics::TBitmap * Code_f
Definition: GraphicUnit.h:969
TTrain::ArrivalMinDwellTime
double ArrivalMinDwellTime
Definition: TrainUnit.h:463
FailCreateLockedRoute
@ FailCreateLockedRoute
Definition: TrainUnit.h:43
TTrainController::TContinuationTrainExpectationEntry::IncrementalDigits
int IncrementalDigits
Repeat headcode separation.
Definition: TrainUnit.h:794
TTrainController::SaveSessionTrains
void SaveSessionTrains(int Caller, std::ofstream &SessionFile)
save trains to a session file
Definition: TrainUnit.cpp:17764
TOneCompleteFormattedTrain::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:275
TRailGraphics::SetWebSafePalette
void SetWebSafePalette(int Caller, Graphics::TBitmap *bmp)
Definition: GraphicUnit.cpp:3473
TRailGraphics::Code_d
Graphics::TBitmap * Code_d
Definition: GraphicUnit.h:967
TTrain::JoinedBy
void JoinedBy(int Caller)
Carry out the actions needed when a train is waiting to be joined by another train.
Definition: TrainUnit.cpp:6571
FailIncorrectExit
@ FailIncorrectExit
Definition: TrainUnit.h:44
TRailGraphics::Code_t
Graphics::TBitmap * Code_t
Definition: GraphicUnit.h:983
TTrainController::GetServiceFromVector
TTrainDataEntry GetServiceFromVector(AnsiString Caller, AnsiString HeadCode, TTrainDataVector Vector, bool &FinishType, bool &FoundFlag)
Return the TrainDataVector entry corresponding to ServiceReference, FinishType is 0 for end of servic...
Definition: TrainUnit.cpp:21186
TRailGraphics::ChangeBackgroundColour
void ChangeBackgroundColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewBackgroundColour, TColor OldBackgroundColour, bool &ColourError)
Definition: GraphicUnit.cpp:3724
TTrain::GetLeadElement
int GetLeadElement()
get LeadElement - used in RouteLockingRequired in TrackUnit.cpp
Definition: TrainUnit.h:723
TimeCmd
@ TimeCmd
Definition: TrainUnit.h:70
TTrain::BackgroundPtr
Graphics::TBitmap * BackgroundPtr[4]
the existing track graphic that the train headcode segment covers up (one for each headcode segment)
Definition: TrainUnit.h:531
TUtilities::LoadFileDouble
double LoadFileDouble(std::ifstream &InFile)
loads a double value from the file (converts from a string to a double) and uses the local decimal po...
Definition: Utilities.cpp:196
TTrain::ZeroPowerDepartMessage
bool ZeroPowerDepartMessage
Definition: TrainUnit.h:362
TTrain::DistanceToStationStop
int DistanceToStationStop
calculated in UpdateTrain & used in CalcDistanceToRedSignalandStopTime to cater for trains running ea...
Definition: TrainUnit.h:481
TTrainController::EntryPos
int EntryPos(int Caller, int TrainIDIn, int TrackVectorNumber)
Return the track entry link (Link[]) array position for the given train on track element at track vec...
Definition: TrainUnit.cpp:11022
SignallerLeave
@ SignallerLeave
Definition: TrainUnit.h:57
TTrain::PlotEntryPos
int PlotEntryPos[4]
the LinkPos value corresponding to the train entry link of the element where each of the 4 headcode c...
Definition: TrainUnit.h:524
NotStarted
@ NotStarted
Definition: TrainUnit.h:92
TTrack::OneNamedLocationElementAtLocation
bool OneNamedLocationElementAtLocation(int Caller, AnsiString LocationName)
True if there is at least one named location element with name 'LocationName', used in timetable inte...
Definition: TrackUnit.cpp:11810
TTrainController::SPADEvents
int SPADEvents
Definition: TrainUnit.h:900
TTrainController::LastTTTime
AnsiString LastTTTime
Stores the last time used in the timetable as an AnsiString - used for timetable analysis.
Definition: TrainUnit.h:840
TTrack::OneStationLongEnoughForSplit
bool OneStationLongEnoughForSplit(int Caller, AnsiString LocationName)
Definition: TrackUnit.cpp:11284
TTrain::ZeroPowerNoRepeatShuttleMessage
bool ZeroPowerNoRepeatShuttleMessage
Definition: TrainUnit.h:360
TTrackElement::SigAspect
enum TTrackElement::@1 SigAspect
TRailGraphics::gl90set
Graphics::TBitmap * gl90set
Definition: GraphicUnit.h:724
TTrain::ActionVectorEntryPtr
TActionVectorEntry * ActionVectorEntryPtr
points to the current position in the ActionVector (a member of the TTrainDataEntry class)
Definition: TrainUnit.h:387
TTrainDataEntry
Contains all data for a single timetable service entry.
Definition: TrainUnit.h:215
LeadMid
@ LeadMid
Definition: TrainUnit.h:308
FailMissedPass
@ FailMissedPass
Definition: TrainUnit.h:43
clSignalStopBackground
#define clSignalStopBackground
Definition: GraphicUnit.h:300
TTrainController::LateDeps
int LateDeps
Definition: TrainUnit.h:890
TAllRoutes::IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber
bool IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(int Caller, int TrackVectorPosition, int XLinkPos, TPrefDirElement &PrefDirElement, int &LockedVectorNumber)
Checks whether the preferred direction element at TrackVectorPosition with XLinkPos value is in a loc...
Definition: TrackUnit.cpp:21202
TrackUnit.h
TTrainController::CheckStartAllowable
bool CheckStartAllowable(int Caller, int RearPosition, int RearExitPos, AnsiString HeadCode, bool ReportFlag, TActionEventType &EventType)
Called when trying to introduce a new train - checks for points in correct orientation,...
Definition: TrainUnit.cpp:16637
TTrack::ResetAllTrainIDsAndFailedPointOrigSpeedLimits
void ResetAllTrainIDsAndFailedPointOrigSpeedLimits(int Caller)
Definition: TrackUnit.cpp:7884
TTrainController::RebuildTimeToExitMultiMap
void RebuildTimeToExitMultiMap(int Caller)
new for multiplayer
Definition: TrainUnit.cpp:22713
TRailGraphics::Code_r
Graphics::TBitmap * Code_r
Definition: GraphicUnit.h:981
TTrain::SendMissedActionLogs
void SendMissedActionLogs(int Caller, int IncNum, TActionVectorEntry *Ptr)
Missed actions (see NameInTimetableBeforeCDT above) sent to the performance log and performance file.
Definition: TrainUnit.cpp:6838
TOneRoute::RouteID
int RouteID
the ID number of the route, this is needed for session saves
Definition: TrackUnit.h:1562
TTrainController::TContinuationTrainExpectationEntry::RepeatNumber
int RepeatNumber
service RepeatNumber
Definition: TrainUnit.h:790
TRailGraphics::CodeX
Graphics::TBitmap * CodeX
Definition: GraphicUnit.h:1023
TOneTrainFormattedEntry::Time
AnsiString Time
the time of the action as a string
Definition: TrainUnit.h:262
TRailGraphics::CodeT
Graphics::TBitmap * CodeT
Definition: GraphicUnit.h:1019
TTrainDataEntry::PowerAtRail
double PowerAtRail
in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
Definition: TrainUnit.h:225
TTrainController::LastTrainLoaded
int LastTrainLoaded
displays last train loaded from session file, used for debugging
Definition: TrainUnit.h:910
TTrainController::NotStartedTrainLateMins
float NotStartedTrainLateMins
total late minutes of trains that haven't started yet on exit operation for locations not reached yet
Definition: TrainUnit.h:867
Utilities.h
TTrainController::TContinuationAutoSigVectorIterator
TContinuationAutoSigVector::iterator TContinuationAutoSigVectorIterator
Definition: TrainUnit.h:780
TTrain::SkippedDeparture
bool SkippedDeparture
< used for terminating a service early and becoming new follow-on service
Definition: TrainUnit.h:341
TTrainController::BgndColNumber
int BgndColNumber
8 bit websafe colour number corresponding to background colour (Utilities->clTransparent)
Definition: TrainUnit.h:936
TTrain::TrainOnContinuation
bool TrainOnContinuation(int Caller)
Returns true if any part of train on a continuation - called when checking for failures,...
Definition: TrainUnit.cpp:10192
ChangeDescription
@ ChangeDescription
Definition: TrainUnit.h:58
TTrainController::CheckSessionLockedRoutes
bool CheckSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
Part of the session file integrity check for locked routes, true for success.
Definition: TrainUnit.cpp:17867
FailTrainEntry
@ FailTrainEntry
Definition: TrainUnit.h:41
MidLag
@ MidLag
Definition: TrainUnit.h:308
TDisplay::Update
void Update()
Repaint the screen display.
Definition: DisplayUnit.h:222
TTrainController::LocServiceTimesDepTimeSort
bool LocServiceTimesDepTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:956
TTrainController::CheckSessionTrains
bool CheckSessionTrains(int Caller, std::ifstream &InFile)
Part of the session file integrity check for train entries, true for success.
Definition: TrainUnit.cpp:17805
StartNew
@ StartNew
Definition: TrainUnit.h:70
TakeSignallerControl
@ TakeSignallerControl
Definition: TrainUnit.h:55
LeadMidLag
@ LeadMidLag
Definition: TrainUnit.h:308
TTrack::GapFlashGreen
TGraphicElement * GapFlashGreen
Definition: TrackUnit.h:809
TTrain::ZeroPowerNoRepeatShuttleOrNewServiceMessage
bool ZeroPowerNoRepeatShuttleOrNewServiceMessage
Definition: TrainUnit.h:361
TrainFailure
@ TrainFailure
Definition: TrainUnit.h:56
TTrain::MinsDelayed
float MinsDelayed
new at v2.2.0 for operator time to act panel. Calculated in UpdateTrain
Definition: TrainUnit.h:469
TTrack::TSigElement::SigPtr
Graphics::TBitmap * SigPtr
pointer to the graphic
Definition: TrackUnit.h:732
TTrainController::TimeToExitMultiMap
TTimeToExitMultiMap TimeToExitMultiMap
Map of times to exit & exit coordinates.
Definition: TrainUnit.h:1064
TAllRoutes::FindRouteNumberFromRoute2MultiMapNoErrors
bool FindRouteNumberFromRoute2MultiMapNoErrors(int Caller, int HLoc, int VLoc, int ELink, int &RouteNumber)
If a route is present at H, V & Elink returns true with RouteNumber giving vector position in AllRout...
Definition: TrackUnit.cpp:20465
TTrainController::Last2CharactersBothDigits
bool Last2CharactersBothDigits(int Caller, AnsiString HeadCode)
Checks the last two characters in HeadCode and returns true if both are digits.
Definition: TrainUnit.cpp:12445
TRailGraphics::Code2
Graphics::TBitmap * Code2
Definition: GraphicUnit.h:992
TTrack::GetVectorPositionsFromInactiveTrackMap
TIMPair GetVectorPositionsFromInactiveTrackMap(int Caller, int HLoc, int VLoc, bool &FoundFlag)
Similar to GetVectorPositionFromTrackMap but for inactive elements, a pair is returned because there ...
Definition: TrackUnit.cpp:5992
TTrain::BackgroundColour
TColor BackgroundColour
< Used for writing to operating image when long refs showing
Definition: TrainUnit.h:544
TTrainOperatingData::EventReported
TActionEventType EventReported
Definition: TrainUnit.h:194
TTrain
Definition: TrainUnit.h:314
TRailGraphics::Code_z
Graphics::TBitmap * Code_z
Definition: GraphicUnit.h:989
TTrainController::CheckTimeValidity
bool CheckTimeValidity(int Caller, AnsiString TimeStr, TDateTime &Time)
returns true if the time complies with requirements
Definition: TrainUnit.cpp:12464
clSignallerStopped
#define clSignallerStopped
Definition: GraphicUnit.h:299
TOnePrefDir::GetFixedPrefDirElementAt
const TPrefDirElement & GetFixedPrefDirElementAt(int Caller, int At) const
Return a non-modifiable element at PrefDirVector position 'At'.
Definition: TrackUnit.cpp:12546
FailBuffersPreventingStart
@ FailBuffersPreventingStart
Definition: TrainUnit.h:44
TTrainController::LocServiceTimesArrTimeSort
bool LocServiceTimesArrTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:951
TOneTrainFormattedEntry::Action
AnsiString Action
includes location if relevant
Definition: TrainUnit.h:260
TTrainController::ContinuationTrainExpectationMultiMap
TContinuationTrainExpectationMultiMap ContinuationTrainExpectationMultiMap
Multimap for TContinuationTrainExpectationEntry objects, the access key is the expectation time.
Definition: TrainUnit.h:924
TPerfLogForm::PerformanceLog
void PerformanceLog(int Caller, AnsiString Statement)
Send Statement to the performance log on screen and to the file.
Definition: PerfLogUnit.cpp:32
TTrain::StoppedForTrainInFront
bool StoppedForTrainInFront
Definition: TrainUnit.h:513
GapJump
@ GapJump
Definition: TrackUnit.h:66
TRailGraphics::CodeK
Graphics::TBitmap * CodeK
Definition: GraphicUnit.h:1010
TTrainController::MissedStops
int MissedStops
Definition: TrainUnit.h:893
NoSequence
@ NoSequence
Definition: TrainUnit.h:81
clCallOnBackground
#define clCallOnBackground
Definition: GraphicUnit.h:293
TTrackElement::Length01
int Length01
Definition: TrackUnit.h:151
TTrackElement::SpeedLimit01
int SpeedLimit01
Definition: TrackUnit.h:151
TTrain::TerminatedMessageSent
bool TerminatedMessageSent
set when a 'train terminated' message has been logged, to prevent its being logged more than once
Definition: TrainUnit.h:425
TTrain::FinishJoin
void FinishJoin(int Caller)
Carry out the actions needed when a train is waiting to join another train.
Definition: TrainUnit.cpp:6521
TTrainController::FinishedOperation
void FinishedOperation(int Caller)
called when exiting operation mode to delete all trains and timetable data etc
Definition: TrainUnit.cpp:10653
TDisplay::DisplayOffsetV
static int DisplayOffsetV
the vertical offset of the displayed screen (as viewpoint moves down [railway moves up] this offset i...
Definition: DisplayUnit.h:79
TTrain::MidElement
int MidElement
Definition: TrainUnit.h:381
TAllRoutes::CallonVector
std::vector< TCallonEntry > CallonVector
the store of all call-on entries
Definition: TrackUnit.h:1714
TTrain::LeadElement
int LeadElement
Definition: TrainUnit.h:381
clDerailedBackground
#define clDerailedBackground
Definition: GraphicUnit.h:295
TTrainController::NotACommand
bool NotACommand(int Caller, AnsiString Text)
Checks whether a piece of text is a command and returns false if it is.
Definition: TrainUnit.cpp:12975
TTrain::RepeatNumber
int RepeatNumber
indicates which of the repeating services this train represents (0 = first service)
Definition: TrainUnit.h:375
TTrainDataEntry::ActionVector
TActionVector ActionVector
all the actions for the train
Definition: TrainUnit.h:235
TTrainController::BaseTime
TDateTime BaseTime
CurrentDateTime (i.e. real time) when operation restarts after a pause.
Definition: TrainUnit.h:753
TTrain::StartSpeed
int StartSpeed
the speed of the train when introduced into the railway (in km/h)
Definition: TrainUnit.h:379
TExitInfo::TExitInfo
TExitInfo()
Definition: TrainUnit.cpp:64
TTrainController::LoadSessionLockedRoutes
void LoadSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
load locked routes from a session file
Definition: TrainUnit.cpp:17846
TTrainController::EarlyPasses
int EarlyPasses
Definition: TrainUnit.h:886
TTrain::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:337
TDisplay::GetOutputLog7
TLabel * GetOutputLog7()
Definition: DisplayUnit.h:175
TTrain::TrainInFront
bool TrainInFront
Definition: TrainUnit.h:514
TTrainController::TOpTimeToActMultiMapEntry
std::pair< float, THCandTrainPosParam > TOpTimeToActMultiMapEntry
Definition: TrainUnit.h:835
FailMissedSplit
@ FailMissedSplit
Definition: TrainUnit.h:42
TTrain::EntryTime
TDateTime EntryTime
Definition: TrainUnit.h:485
TExitInfo::RepeatNumber
short RepeatNumber
Definition: TrainUnit.h:110
TTrainController::TContinuationAutoSigEntry::FirstDelay
double FirstDelay
Definition: TrainUnit.h:768
TTrain::LastSigPassedFailed
bool LastSigPassedFailed
flag used to erase route elements in an autosigs route after a failed signal
Definition: TrainUnit.h:409
TTrainController::SaveTrainDataVectorToFile
void SaveTrainDataVectorToFile(int Caller)
diagnostic function to store all train data to a file for examination, not used normally
Definition: TrainUnit.cpp:17569
TTrainController::NearTransparentColNumber
int NearTransparentColNumber
Definition: TrainUnit.h:940
TOnePrefDir::PrefDirSize
unsigned int PrefDirSize() const
Return the vector size.
Definition: TrackUnit.h:1420
TTrack::PlotSignal
void PlotSignal(int Caller, TTrackElement TrackElement, TDisplay *Disp)
Plot signals on screen according to their aspect (Attribute value)
Definition: TrackUnit.cpp:6280
End
@ End
Definition: TrackUnit.h:76
TUtilities::FixedMinRepairTime
int FixedMinRepairTime
Definition: Utilities.h:72
TTrain::OneLengthAccelDecel
bool OneLengthAccelDecel
set when a train can only move forwards one element before stopping but needs to accelerate for the f...
Definition: TrainUnit.h:415
TTrain::FloatingLabelNextString
AnsiString FloatingLabelNextString(int Caller, TActionVectorEntry *Ptr)
Used in the floating window to display the 'Next' action.
Definition: TrainUnit.cpp:7492
TTrack::TimetabledLocationNameAllocated
bool TimetabledLocationNameAllocated(int Caller, AnsiString LocationName)
True if a non-empty LocationName found as a timetabled location name i.e. not as a continuation name.
Definition: TrackUnit.cpp:9086
StaticFeaturesDisplay
TDisplay * StaticFeaturesDisplay
The object pointer for track, text & graphics only for replacing the text of long service references.
Definition: DisplayUnit.cpp:56
TTrain::FailedTrainNoFinishJoinMessage
bool FailedTrainNoFinishJoinMessage
Definition: TrainUnit.h:355
TTrain::EnterLongServRefAsName
void EnterLongServRefAsName(int Caller, TDisplay *Disp)
This is to display the train's service ref above the train.
Definition: TrainUnit.cpp:2509
TDisplay::GetImage
TImage * GetImage()
Return a pointer to the screen image.
Definition: DisplayUnit.h:139
TTrack::InactiveTrackElementAt
TTrackElement & InactiveTrackElementAt(int Caller, int At)
A range-checked version of InactiveTrackVector.at(At)
Definition: TrackUnit.cpp:11249
ExitRailway
@ ExitRailway
Definition: TrainUnit.h:71
TPrefDirElement::GetTrackVectorPosition
unsigned int GetTrackVectorPosition() const
Returns TrackVectorPosition.
Definition: TrackUnit.h:305
TTrain::StationStopCalculated
bool StationStopCalculated
used in calculating DistanceToStationStop for trains running early before they have reached the stop ...
Definition: TrainUnit.h:421
TTrainController::LoadSessionContinuationAutoSigEntries
void LoadSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
load ContinuationAutoSigEntries from a session file
Definition: TrainUnit.cpp:17929
FailMissedDSC
@ FailMissedDSC
Definition: TrainUnit.h:42
TTrackElement
Basic track elements as implemented in the overall railway layout.
Definition: TrackUnit.h:125
TRailGraphics::Code_n
Graphics::TBitmap * Code_n
Definition: GraphicUnit.h:977
FailMissedNewService
@ FailMissedNewService
Definition: TrainUnit.h:43
TUtilities::Format96HHMMSS
AnsiString Format96HHMMSS(TDateTime DateTime)
formats a TDateTime into an AnsiString of the form hh:mm:ss where hh runs from 00 to 95 & resets when...
Definition: Utilities.cpp:812
TRailGraphics::smSolidBgnd
Graphics::TBitmap * smSolidBgnd
Definition: GraphicUnit.h:1027
clTRSBackground
#define clTRSBackground
Definition: GraphicUnit.h:304
TTrainController::OtherMissedEvents
int OtherMissedEvents
Definition: TrainUnit.h:898
TTrainController::EarlyExits
int EarlyExits
Definition: TrainUnit.h:887
TTrain::ExitSpeedHalf
double ExitSpeedHalf
speed when half way into the next element
Definition: TrainUnit.h:439
SignalPost
@ SignalPost
Definition: TrackUnit.h:66
TRailGraphics::Code_h
Graphics::TBitmap * Code_h
Definition: GraphicUnit.h:971
TTrain::LastActionTime
TDateTime LastActionTime
time of the last timetabled event, used to ensure at least a 30 second delay before the next action
Definition: TrainUnit.h:490
TDisplay::GetOutputLog1
TLabel * GetOutputLog1()
Return pointers to warning message logs (appear above the railway display during operation)
Definition: DisplayUnit.h:144
TAllRoutes::TRoute2MultiMapIterator
TRoute2MultiMap::iterator TRoute2MultiMapIterator
Definition: TrackUnit.h:1691
TTrain::TrainFailurePending
bool TrainFailurePending
set when failure due & takes effect when all PlotElements properly set, added at v2....
Definition: TrainUnit.h:365
FailMissedTerminate
@ FailMissedTerminate
Definition: TrainUnit.h:43
TTrainController::SecondPassActions
bool SecondPassActions(int Caller, bool GiveMessages, bool &TwoLocationFlag)
Carry out further detailed timetable consistency checks, return true for success.
Definition: TrainUnit.cpp:13724
TRailGraphics::Code9
Graphics::TBitmap * Code9
Definition: GraphicUnit.h:999
TTrainDataEntry::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:217
FailTrainInFront
@ FailTrainInFront
Definition: TrainUnit.h:45
TTrain::TrainHasFailed
void TrainHasFailed(int Caller)
Called when there is a random train failure.
Definition: TrainUnit.cpp:6143
TTrain::LongServRefWorkingBitmap
Graphics::TBitmap * LongServRefWorkingBitmap
< Stores the long service ref name for > 4 chars
Definition: TrainUnit.h:539
TUtilities::MinorDelayFactor
float MinorDelayFactor
Definition: Utilities.h:53
TTrain::LongServRefEnteredFlag
bool LongServRefEnteredFlag
defines whether service ref plotted or not
Definition: TrainUnit.h:497
clNormalBackground
#define clNormalBackground
Definition: GraphicUnit.h:298
TTrainController::CheckNonRepeatingShuttleLinksAndSetData
bool CheckNonRepeatingShuttleLinksAndSetData(int Caller, AnsiString MainHeadCode, AnsiString NonRepeatingHeadCode, bool SetDataAndCheckLocations, bool GiveMessages)
A timetable validation function where cross references are checked for validity for non-repeating shu...
Definition: TrainUnit.cpp:16914
TTrainController::CheckFourthValidityForSplit
bool CheckFourthValidityForSplit(AnsiString SplitDistributionString, bool GiveMessages)
Checks fourth segment in timetable for train splits - percentage mass then '-' then percentage power ...
Definition: TrainUnit.cpp:12992
TActionVectorEntry::NonRepeatingShuttleLinkEntryPtr
TTrainDataEntry * NonRepeatingShuttleLinkEntryPtr
pointer used by shuttles for the non-shuttle train links, in & out, the corresponding non-shuttle lin...
Definition: TrainUnit.h:153
TTrainController::SPADRisks
int SPADRisks
Definition: TrainUnit.h:901
TTrainController::TLocServiceTimesVector
std::vector< TLocServiceTimes > TLocServiceTimesVector
Definition: TrainUnit.h:821
TTrainController::LocServiceTimesAtLocTimeSort
bool LocServiceTimesAtLocTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:960
TTrain::EntrySpeed
double EntrySpeed
speed at which the train enters the next element
Definition: TrainUnit.h:437
TConfiguration
TConfiguration
< describes the type of track link. 'End' is used for both buffer stop and continuation entry/exit po...
Definition: TrackUnit.h:75
TTrain::TrainInFrontMessage
bool TrainInFrontMessage
flags to indicate whether the respective message has been sent
Definition: TrainUnit.h:363
TTrain::TrainGone
bool TrainGone
set when train has left the railway, so it can be removed from the display at the next clock tick
Definition: TrainUnit.h:509
TTrackType
TTrackType
< describes the type of track element
Definition: TrackUnit.h:65
TTrainController::OnTimePasses
int OnTimePasses
Definition: TrainUnit.h:896
TTrainController::SigSHigh
bool SigSHigh
Definition: TrainUnit.h:860
TRailGraphics::smCaramel
Graphics::TBitmap * smCaramel
Definition: GraphicUnit.h:898
TGraphicElement::PlotOriginal
void PlotOriginal(int Caller, TDisplay *Disp)
Plot the original graphic on screen.
Definition: TrackUnit.cpp:1928
TExitInfo
Definition: TrainUnit.h:107
clBufferAttentionNeeded
#define clBufferAttentionNeeded
Definition: GraphicUnit.h:291
TUtilities::ModerateDelayCutoff
float ModerateDelayCutoff
Definition: Utilities.h:51
TTrain::StoppedAfterSPAD
bool StoppedAfterSPAD
Definition: TrainUnit.h:513
FailSPAD
@ FailSPAD
Definition: TrainUnit.h:41
TTrainController::OnTimeArrivals
int OnTimeArrivals
Definition: TrainUnit.h:894
clTrainFailedBackground
#define clTrainFailedBackground
Definition: GraphicUnit.h:305
TRailGraphics::Code_x
Graphics::TBitmap * Code_x
Definition: GraphicUnit.h:987
clFrontCodeSignaller
#define clFrontCodeSignaller
Definition: GraphicUnit.h:296
TimeCmdHeadCode
@ TimeCmdHeadCode
Definition: TrainUnit.h:70
Utilities
TUtilities * Utilities
Definition: Utilities.cpp:47
TTrainController::LongServRefFontColNumber
int LongServRefFontColNumber
the websafe colour number for long serv ref names
Definition: TrainUnit.h:938
TFixedTrackPiece::SmallGraphicPtr
Graphics::TBitmap * SmallGraphicPtr
the track bitmap for display on the zoomed-out railway
Definition: TrackUnit.h:94
TActionVectorEntry::ArrivalTime
TDateTime ArrivalTime
Definition: TrainUnit.h:139
TTrain::StoppedAtBuffers
bool StoppedAtBuffers
Definition: TrainUnit.h:513
TTrainController::CreateTTAnalysisFile
bool CreateTTAnalysisFile(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir, bool ArrChecked, bool DepChecked, bool AtLocChecked, bool DirChecked, int ArrRange, int DepRange)
Generate a timetable analysis file in the 'Formatted Timetables' folder, return false if failed for a...
Definition: TrainUnit.cpp:18750
TRailGraphics::Code_b
Graphics::TBitmap * Code_b
Definition: GraphicUnit.h:965
TTrain::Mass
int Mass
in kg
Definition: TrainUnit.h:479
TRailGraphics::Code0
Graphics::TBitmap * Code0
Definition: GraphicUnit.h:990
TTrainController::ProcessOneTimetableLine
bool ProcessOneTimetableLine(int Caller, int Count, AnsiString OneLine, bool &EndOfFile, bool FinalCall, bool GiveMessages, bool CheckLocationsExistInRailway)
Carry out preliminary (mainly syntax) validity checks on a single timetable service entry and (if Fin...
Definition: TrainUnit.cpp:11676
TTrainController::TContinuationTrainExpectationEntry::IncrementalMinutes
int IncrementalMinutes
Repeat separation in minutes.
Definition: TrainUnit.h:792
TTrain::LeadExitPos
int LeadExitPos
Definition: TrainUnit.h:381
TTrain::FrontElementLength
int FrontElementLength
values associated with the element immediately in front of the train (speed in km/h,...
Definition: TrainUnit.h:477
TTimetableShuttleLinkType
TTimetableShuttleLinkType
Definition: TrainUnit.h:85
TRailGraphics::CodeP
Graphics::TBitmap * CodeP
Definition: GraphicUnit.h:1015
Pass
@ Pass
Definition: TrainUnit.h:57
TTrain::SkipPtrValue
int SkipPtrValue
stores the pointer increment from first action in ActionVector for skipped actions when a departure i...
Definition: TrainUnit.h:389
TNumList
std::list< int > TNumList
a list of valid train exit TrackVector positions for 'Fer' entries
Definition: TrainUnit.h:95
TTrainController::DerailWarning
bool DerailWarning
Definition: TrainUnit.h:846
TTrainController::TContinuationTrainExpectationMultiMapPair
std::pair< TDateTime, TContinuationTrainExpectationEntry > TContinuationTrainExpectationMultiMapPair
a single multimap entry
Definition: TrainUnit.h:806
TTrain::ImageLongServRefBitmap
Graphics::TBitmap * ImageLongServRefBitmap
< General purpose storage for long serv ref display
Definition: TrainUnit.h:541
RouteForceCancelled
@ RouteForceCancelled
Definition: TrainUnit.h:45
EnRoute
@ EnRoute
Definition: TrainUnit.h:76
TTrain::IsTrainIDOnBridgeTrackPos23
bool IsTrainIDOnBridgeTrackPos23(int Caller, unsigned int TrackVectorPosition)
True if train is on a bridge on trackpos 2 & 3.
Definition: TrainUnit.cpp:3654
TTrack::DiagonalFouledByTrain
bool DiagonalFouledByTrain(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber, int &TrainID)
As DiagonalFouledByRouteOrTrain (in TAllRoutes) but only checks for a train (may or may not be a rout...
Definition: TrackUnit.cpp:12258
TTrainController::TotLateDepMins
float TotLateDepMins
Definition: TrainUnit.h:878
TDisplay::ZoomOutFlag
bool ZoomOutFlag
true when zoomed-out
Definition: DisplayUnit.h:70
TRailGraphics::CodeC
Graphics::TBitmap * CodeC
Definition: GraphicUnit.h:1002
TTrain::PlotElement
int PlotElement[4]
the TrackVectorPosition of the element where each of the 4 headcode characters is plotted (need to be...
Definition: TrainUnit.h:522
TTrainController::TServiceCallingLocsList
std::list< AnsiString > TServiceCallingLocsList
Used in determining train directions in timetable conflict analysis.
Definition: TrainUnit.h:824
TTrainController::TrainVector
TTrainVector TrainVector
vector containing all trains currently in the railway
Definition: TrainUnit.h:932
TUtilities::ModerateDelayFactor
float ModerateDelayFactor
Definition: Utilities.h:54
TUtilities::MajorDelayCutoff
float MajorDelayCutoff
Definition: Utilities.h:52
TRailGraphics::Code_l
Graphics::TBitmap * Code_l
Definition: GraphicUnit.h:975
TTrainController::TContinuationAutoSigEntry
< TTClockTime when last session saved - to prevent display of warning message on exit session if < 5 ...
Definition: TrainUnit.h:766
TUtilities::CallLogPop
void CallLogPop(int Caller)
pops the last entry off the call stack, throws an error if called when empty
Definition: Utilities.cpp:50
TTrain::IncrementalMinutes
int IncrementalMinutes
the number of minutes to increment by in repeat entries
Definition: TrainUnit.h:369
TTrain::MaximumSpeedLimit
static const int MaximumSpeedLimit
Definition: TrainUnit.h:329
TRailGraphics::CodeI
Graphics::TBitmap * CodeI
Definition: GraphicUnit.h:1008
TTrainController::TotEarlyArrMins
float TotEarlyArrMins
values for performance file summary
Definition: TrainUnit.h:873
TTrain::WriteTrainToImage
void WriteTrainToImage(int Caller, Graphics::TBitmap *Bitmap)
Called by TrainController::WriteTrainsToImage (called by TInterface::SaveOperatingImage1Click) to add...
Definition: TrainUnit.cpp:9723
TRailGraphics::CodeL
Graphics::TBitmap * CodeL
Definition: GraphicUnit.h:1011
TTrainDataEntry::MaxBrakeRate
double MaxBrakeRate
< true if a description is given for the train, if only headcode given for a follow-on service then f...
Definition: TrainUnit.h:221
FNSNonRepeatToShuttle
@ FNSNonRepeatToShuttle
Definition: TrainUnit.h:70
TDisplay::PlotOutput
void PlotOutput(int Caller, int HPos, int VPos, Graphics::TBitmap *PlotItem)
Plot the graphic at screen position HPos & VPos.
Definition: DisplayUnit.cpp:87
TUtilities::SaveFileBool
void SaveFileBool(std::ofstream &OutFile, bool SaveBool)
gives a delay od Msec value;
Definition: Utilities.cpp:108
TTrainDataEntry::SignallerSpeed
int SignallerSpeed
in km/h for use when under signaller control
Definition: TrainUnit.h:231
TTrain::AbleToMove
bool AbleToMove(int Caller)
Indicates that a train is not prevented from moving - used to allow appropriate popup menu options wh...
Definition: TrainUnit.cpp:7341
TTrainController::MTBFHours
double MTBFHours
<Message flags in TT checks to stop being given twice
Definition: TrainUnit.h:865
TTrack::TInfrastructureFailureEntry::TVPos
int TVPos
Definition: TrackUnit.h:715
TOneRoute::ForceCancelRoute
void ForceCancelRoute(int Caller)
Cancel a route immediately if a train occupies it when travelling in the wrong direction (or occupies...
Definition: TrackUnit.cpp:19503
TTrain::TrainMode
TTrainMode TrainMode
mode of operation - either Timetable (running under timetable control) or Signaller (running under si...
Definition: TrainUnit.h:494
TTrainController::CheckNonRepeatingShuttleLinkTime
bool CheckNonRepeatingShuttleLinkTime(int Caller, TDateTime ReverseEventTime, TDateTime ForwardEventTime, int RepeatMins, int RepeatNumber)
The forward train is the finish shuttle entry 'Fns-sh', the reverse (new non-repeating service) time ...
Definition: TrainUnit.cpp:17183
FailNoPowerUnableToDepart
@ FailNoPowerUnableToDepart
Definition: TrainUnit.h:45
TActionVectorEntry::NewMaxSpeed
AnsiString NewMaxSpeed
Definition: TrainUnit.h:125
TUtilities::Format96HHMM
AnsiString Format96HHMM(TDateTime DateTime)
formats a TDateTime into an AnsiString of the form hh:mm where hh runs from 00 to 95 & resets when it...
Definition: Utilities.cpp:831
TTrain::CallOnMaxSpeed
static const int CallOnMaxSpeed
km/h
Definition: TrainUnit.h:321
TTrain::CheckAndCancelRouteForWrongEndEntry
void CheckAndCancelRouteForWrongEndEntry(int Caller, int Element, int EntryPos)
Checks whether Element and EntryPos (where train is about to enter) is on an existing route (or cross...
Definition: TrainUnit.cpp:3862
TRailGraphics::CodeW
Graphics::TBitmap * CodeW
Definition: GraphicUnit.h:1022
TTrain::ReleaseTime
TDateTime ReleaseTime
Definition: TrainUnit.h:487
TTrain::GetTrainHeadCode
AnsiString GetTrainHeadCode(int Caller)
Returns the train headcode, taking account of the RepeatNumber.
Definition: TrainUnit.cpp:5670
TTrain::LowEntryValue
bool LowEntryValue(int EntryLink) const
Returns true if EntryLink is 1, 2, 4 or 7, in these circumstances the front of the train (i....
Definition: TrainUnit.cpp:3175
RearSplit
@ RearSplit
Definition: TrainUnit.h:55
TActionVectorEntry::FrontStartOrRepeatDigits
int FrontStartOrRepeatDigits
dual-purpose variables used for the TrackVectorPositions of the rear and front train starting element...
Definition: TrainUnit.h:137
SignallerControlStop
@ SignallerControlStop
Definition: TrainUnit.h:57
TTrack::TrackVector
TTrackVector TrackVector
Definition: TrackUnit.h:829
TTrain::HasTrainGone
bool HasTrainGone()
Check whether the train has left the railway, so that it can be removed from the display at the next ...
Definition: TrainUnit.h:707
TRailGraphics::Code_i
Graphics::TBitmap * Code_i
Definition: GraphicUnit.h:972
TTrainController::ExcessLCDownMins
float ExcessLCDownMins
total excess time in minutes over the 3 minutes barriers down allowance for level crossings
Definition: TrainUnit.h:871
TRailGraphics::Code5
Graphics::TBitmap * Code5
Definition: GraphicUnit.h:995
TTrackElement::CallingOnSet
bool CallingOnSet
Used for for signals only when a train is being called on - used to plot the position lights.
Definition: TrackUnit.h:135
TUtilities::SaveFileInt
void SaveFileInt(std::ofstream &OutFile, int SaveInt)
stores the int value to the file, then a CR
Definition: Utilities.cpp:121
TTrain::IsLongServRefDisplayRequired
void IsLongServRefDisplayRequired(int Caller, TDisplay *Disp)
function that checks if long serv ref display needed and if so removes earlier display if plotted the...
Definition: TrainUnit.cpp:2492
TrainController
TTrainController * TrainController
the object pointer, one object only - created in InterfaceUnit
Definition: TrainUnit.cpp:56
TUtilities::FailureMode
TFailureMode FailureMode
specifies whether no failures or minor, moderate or major random failures are to be applied (added at...
Definition: Utilities.h:125
TTrain::BufferAtExit
bool BufferAtExit(int Caller, int Element, int Exitpos) const
True if Element is a buffer and Exitpos is the buffer end.
Definition: TrainUnit.cpp:3586
TTrain::IsTrainTerminating
bool IsTrainTerminating(int Caller)
True if train service terminates at its current location.
Definition: TrainUnit.cpp:7313
TTrainController::SecondPassMessage
void SecondPassMessage(bool GiveMessages, AnsiString Message)
Give a user message during timetable integrity checking if GiveMessages is true, ignore if false.
Definition: TrainUnit.cpp:17281
SignallerStepForward
@ SignallerStepForward
Definition: TrainUnit.h:57
TTrack::GetHLocMin
int GetHLocMin()
Definition: TrackUnit.h:895
TDisplay::GetOutputLog5
TLabel * GetOutputLog5()
Definition: DisplayUnit.h:165
TTrainController::LateExits
int LateExits
Definition: TrainUnit.h:892
TTrainController::OpTimeToActUpdateCounter
unsigned int OpTimeToActUpdateCounter
<List of all ServiceRefs that have two or more same locations without a cdt between - loaded during S...
Definition: TrainUnit.h:916
TTrain::BeingCalledOn
bool BeingCalledOn
in course of being called on to a station
Definition: TrainUnit.h:395
TUtilities::CheckFileInt
bool CheckFileInt(std::ifstream &InFile, int Lowest, int Highest)
checks that the value is an int lying between Lowest & Highest (inclusive), returns true for success
Definition: Utilities.cpp:262
TRailGraphics::ChangeForegroundColour2
void ChangeForegroundColour2(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewForegroundColour, TColor BackgroundColour)
New function to do the same as the above but with fewer pixel changes - for use in LoadSession to avo...
Definition: GraphicUnit.cpp:3674
TRailGraphics::Code_e
Graphics::TBitmap * Code_e
Definition: GraphicUnit.h:968
TTrain::CalcTimeToAct
float CalcTimeToAct(int Caller, float &TimeToExit, THVShortPair &ExitPair)
new v2.2.0 for operator action panel. Calculates the time left for operator action to avoid unnecessa...
Definition: TrainUnit.cpp:9791
TUtilities::SignalChangeEventsPerFailure
int SignalChangeEventsPerFailure
number of signal changes between failures - reciprocal of failure probability per change
Definition: Utilities.h:101
TTrain::HoldAtLocationInTTMode
bool HoldAtLocationInTTMode
true if actions are needed before train departs
Definition: TrainUnit.h:345
TTrack::TInfrastructureFailureEntry
Definition: TrackUnit.h:714
TTrain::PlotTrainInZoomOutMode
void PlotTrainInZoomOutMode(int Caller, bool Flash)
Plots the train on screen in zoomed-out mode, state of 'Flash' determines whether the flashing trains...
Definition: TrainUnit.cpp:9532
TTrain::LoadOneSessionTrain
void LoadOneSessionTrain(int Caller, std::ifstream &InFile)
Create one train with relevant member values from the sesion file.
Definition: TrainUnit.cpp:8811
TTrain::PlotBackgroundGraphic
void PlotBackgroundGraphic(int Caller, int ArrayNumber, TDisplay *Disp) const
Replot the graphic pointed to by BackgroundPtr (see above) after a train has passed.
Definition: TrainUnit.cpp:3578
TTrainController::TotLateArrMins
float TotLateArrMins
Definition: TrainUnit.h:877
TTrain::HOffset
int HOffset[4]
Definition: TrainUnit.h:518
FailMissedArrival
@ FailMissedArrival
Definition: TrainUnit.h:42
TTrainController::TrainDataVectorCopy
TTrainDataVector TrainDataVectorCopy
vector containing the internal timetable, the copy is used for conflict analysis only
Definition: TrainUnit.h:930
TTrain::UpdateCounter
unsigned int UpdateCounter
used in train splitting operations to prevent too frequent checks for a location being long enough fo...
Definition: TrainUnit.h:483
NewService
@ NewService
Definition: TrainUnit.h:55
FailEnterLockedRoute
@ FailEnterLockedRoute
Definition: TrainUnit.h:43
TTrainController::PlotAllTrainsInZoomOutMode
void PlotAllTrainsInZoomOutMode(int Caller, bool Flash)
Plots all trains on screen in zoomed-out mode, state of 'Flash' determines whether the flashing train...
Definition: TrainUnit.cpp:18082
TOneRoute
A descendent of TOnePrefDir used for routes. Used during contruction of a route (ConstructRoute) and ...
Definition: TrackUnit.h:1522
TTrain::SignallerStopped
bool SignallerStopped
Definition: TrainUnit.h:513
TTrain::BufferZoomOutFlashRequired
bool BufferZoomOutFlashRequired
set when train is at buffers and is to flash in zoomout mode (i.e. when reaches buffers unexpectedly ...
Definition: TrainUnit.h:397
TAllRoutes::TRouteType
TRouteType
Definition: TrackUnit.h:1674
TTrain::TrainDataEntryPtr
TTrainDataEntry * TrainDataEntryPtr
points to the current position in the timetable's TrainDataVector
Definition: TrainUnit.h:385
TTrain::LongServRefNameBitmap
Graphics::TBitmap * LongServRefNameBitmap
Definition: TrainUnit.h:537
TTrainDataEntry::StartSpeed
int StartSpeed
in km/h
Definition: TrainUnit.h:233
TTrainDataEntry::NumberOfTrains
int NumberOfTrains
number of repeats + 1
Definition: TrainUnit.h:229
TTrainController::Derailments
int Derailments
Definition: TrainUnit.h:884
clStationStopBackground
#define clStationStopBackground
Definition: GraphicUnit.h:302
TUtilities::CumulativeDelayedRandMinsAllTrains
int CumulativeDelayedRandMinsAllTrains
the running total of all random delays including knock-on delays for all trains, used to reduce total...
Definition: Utilities.h:107
TDisplay::GetOutputLog6
TLabel * GetOutputLog6()
Definition: DisplayUnit.h:170
ShuttleLink
@ ShuttleLink
Definition: TrainUnit.h:86
TRailGraphics::CodeM
Graphics::TBitmap * CodeM
Definition: GraphicUnit.h:1012
Crossover
@ Crossover
Definition: TrackUnit.h:66
TTrain::ClearToNextSignal
bool ClearToNextSignal(int Caller)
Checks forward from train LeadElement, following leading point attributes but ignoring trailing point...
Definition: TrainUnit.cpp:5270
AtLocation
@ AtLocation
Definition: TrainUnit.h:76
Signal
@ Signal
Definition: TrackUnit.h:76
TRailGraphics::Code_k
Graphics::TBitmap * Code_k
Definition: GraphicUnit.h:974
TRailGraphics::CodeF
Graphics::TBitmap * CodeF
Definition: GraphicUnit.h:1005
TTrack::ContinuationNameMap
std::map< AnsiString, char > ContinuationNameMap
map of all continuation names, char is a dummy
Definition: TrackUnit.h:797
TAllRoutes::GetRouteTypeAndNumber
TRouteType GetRouteTypeAndNumber(int Caller, int TrackVectorPosition, int LinkPos, int &RouteNumber)
Examines Route2MultiMap and if the element at TrackVectorPosition with LinkPos (can be entry or exit)...
Definition: TrackUnit.cpp:20159
TTrainController::TrainDataVector
TTrainDataVector TrainDataVector
Definition: TrainUnit.h:930
TTrainController::ConsolidateSARNTAtLoc
AnsiString ConsolidateSARNTAtLoc(int Caller, const AnsiString Input, int &NumTrainsAtLoc)
Removes duplicates from and sorts ServiceAndRepeatNumTotal into alphabetical order for AtLoc listing ...
Definition: TrainUnit.cpp:21465
TAllRoutes::NotAutoSigsRoute
@ NotAutoSigsRoute
Definition: TrackUnit.h:1675
TTrain::ExitTimeHalf
TDateTime ExitTimeHalf
Definition: TrainUnit.h:485
Exited
@ Exited
Definition: TrainUnit.h:92
TTrack::TrackElementAt
TTrackElement & TrackElementAt(int Caller, int At)
A range-checked version of TrackVector.at(At)
Definition: TrackUnit.cpp:11235
TTrain::ActionsSkippedFlag
bool ActionsSkippedFlag
prevents any further skipping until after the next departure
Definition: TrainUnit.h:343
TTrainController::LoadSessionTrains
void LoadSessionTrains(int Caller, std::ifstream &SessionFile)
load trains from a session file
Definition: TrainUnit.cpp:17780
TTrain::CheckOneSessionTrain
static bool CheckOneSessionTrain(std::ifstream &InFile)
Carries out an integrity check for the train section of a session file, if fails a message is given a...
Definition: TrainUnit.cpp:9098
TAllRoutes::GetRouteTypeAndGraphics
TRouteType GetRouteTypeAndGraphics(int Caller, int TrackVectorPosition, int LinkPos, Graphics::TBitmap *&EXGraphicPtr, Graphics::TBitmap *&EntryDirectionGraphicPtr)
Examines Route2MultiMap for the element at TrackVectorPosition with LinkPos (can be entry or exit).
Definition: TrackUnit.cpp:19985
TRailGraphics::CodeY
Graphics::TBitmap * CodeY
Definition: GraphicUnit.h:1024
TTrain::SetOneGraphicCode
Graphics::TBitmap * SetOneGraphicCode(char CodeChar)
Return a pointer to the graphic corresponding to the character 'CodeVhar'.
Definition: TrainUnit.cpp:2793
TTrain::DerailPending
bool DerailPending
Definition: TrainUnit.h:513
TTrainMode
TTrainMode
indicates train operating mode, 'None' for not in use
Definition: TrainUnit.h:63
TAllRoutes::TCallonEntry
Used to store relevant values when a call-on found, ready for plotting an unrestricted route.
Definition: TrackUnit.h:1696
TRailGraphics::Code_a
Graphics::TBitmap * Code_a
Definition: GraphicUnit.h:964
TTrain::StoppedAtLocation
bool StoppedAtLocation
Definition: TrainUnit.h:513
TTrainController::TContinuationTrainExpectationMultiMapIterator
TContinuationTrainExpectationMultiMap::iterator TContinuationTrainExpectationMultiMapIterator
iterator for the multimap
Definition: TrainUnit.h:804
TTrain::MaximumMassLimit
static const int MaximumMassLimit
kg (i.e. 10,000 tonnes)
Definition: TrainUnit.h:323
TTrainController::StopTTClockFlag
bool StopTTClockFlag
when true the timetable clock is stopped, used for messages display and train popup menu display etc
Definition: TrainUnit.h:848
FrontSplit
@ FrontSplit
Definition: TrainUnit.h:55
TTrain::MaxExitSpeed
double MaxExitSpeed
the maximum speed that the train can exit the next element
Definition: TrainUnit.h:447
TTrainController::BFHigh
bool BFHigh
Definition: TrainUnit.h:860
TRailGraphics::Code4
Graphics::TBitmap * Code4
Definition: GraphicUnit.h:994
TRailGraphics::Code_g
Graphics::TBitmap * Code_g
Definition: GraphicUnit.h:970
TTrain::LagEntryPos
int LagEntryPos
Definition: TrainUnit.h:381
SNTShuttle
@ SNTShuttle
Definition: TrainUnit.h:70
TTrain::NewDelay
double NewDelay
an additional random delay at a location (added at v2.13.0)
Definition: TrainUnit.h:459
TRailGraphics::CodeG
Graphics::TBitmap * CodeG
Definition: GraphicUnit.h:1006
Leave
@ Leave
Definition: TrainUnit.h:55
TTrainController::UnexpectedExits
int UnexpectedExits
Definition: TrainUnit.h:903
TTrainController::TLocServiceTimes::AtLocTime
AnsiString AtLocTime
Definition: TrainUnit.h:816
TTrainController::ServiceReference
AnsiString ServiceReference
String used to display the offending service in timetable error messages.
Definition: TrainUnit.h:844
TTrain::FirstLaterStopRecoverableTime
float FirstLaterStopRecoverableTime
this used to deduct from RecoverableTime when arrive at a location for OperatorActionpanel (OperatorA...
Definition: TrainUnit.h:475
TTrack::FailedSignalsVector
TFailedElementVector FailedSignalsVector
Definition: TrackUnit.h:795
TTrain::LeavingUnderSigControlAtContinuation
bool LeavingUnderSigControlAtContinuation
set when the train has reached an exit continuation when under signaller control, used to prevent the...
Definition: TrainUnit.h:411
TTrain::LongServRefTextH
int LongServRefTextH
stores the HPos position of the service ref train name
Definition: TrainUnit.h:499
TTrain::PlotTrainGraphic
void PlotTrainGraphic(int Caller, int ArrayNumber, TDisplay *Disp)
Plot the train's headcode character corresponding to ArrayNumber.
Definition: TrainUnit.cpp:3559
TRailGraphics::Code_j
Graphics::TBitmap * Code_j
Definition: GraphicUnit.h:973
FNil
@ FNil
Definition: Utilities.h:43
NoFormat
@ NoFormat
Definition: TrainUnit.h:70
TTrainController::TwoOrMoreLocationsWarningGiven
bool TwoOrMoreLocationsWarningGiven
new at v2.6.0 to allow loops
Definition: TrainUnit.h:856
FailBufferCrash
@ FailBufferCrash
Definition: TrainUnit.h:44
WaitingForFJO
@ WaitingForFJO
Definition: TrainUnit.h:44
FailMissedExitRailway
@ FailMissedExitRailway
Definition: TrainUnit.h:43
TTrain::NonDefaultMinDwellTimeFlag
bool NonDefaultMinDwellTimeFlag
set when an explicit min dwell time is set
Definition: TrainUnit.h:413
TTrainController::ControllerGetNewServiceDepartureInfo
AnsiString ControllerGetNewServiceDepartureInfo(int Caller, TActionVectorIterator Ptr, int RptNum, TTrainDataEntry *TDEPtr, TTrainDataEntry *LinkedTrainDataPtr, int IncrementalMinutes, int IncrementalDigits, AnsiString RetStr)
Similar to TTrain::GetNewServiceDepartureInfo for use in ContinuationEntryFloatingTTString.
Definition: TrainUnit.cpp:11305
TimeTimeLoc
@ TimeTimeLoc
Definition: TrainUnit.h:70
TTrain::GetOffsetValues
void GetOffsetValues(int Caller, int &HOffset, int &VOffset, int Link) const
Sets HOffset & VOffset (see above) for a single headcode character depending on the Link value.
Definition: TrainUnit.cpp:3104
TTrain::TimeToExit
float TimeToExit
in minutes: new for multiplayer, -1 = > 60 mins
Definition: TrainUnit.h:473
TTrain::Derailed
bool Derailed
Definition: TrainUnit.h:513
TRailGraphics::ChangeSpecificColour
void ChangeSpecificColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor ColourToBeChanged, TColor NewColour)
Definition: GraphicUnit.cpp:3703
Enter
@ Enter
Definition: TrainUnit.h:55
TRailGraphics::bm93set
Graphics::TBitmap * bm93set
Definition: GraphicUnit.h:518
TTrack::GetTrackVectorPositionFromString
int GetTrackVectorPositionFromString(int Caller, AnsiString String, bool GiveMessages)
Takes the ElementID value (an AnsiString) (e.g. "8-13", "N43-N127", etc) and returns the correspondin...
Definition: TrackUnit.cpp:8099
TRailGraphics::Code_q
Graphics::TBitmap * Code_q
Definition: GraphicUnit.h:980
TTrainController::SaveSessionLockedRoutes
void SaveSessionLockedRoutes(int Caller, std::ofstream &SessionFile)
save locked routes to a session file
Definition: TrainUnit.cpp:17829
TTrainController::LongServRefFont
TFont * LongServRefFont
the font used for long serv ref names
Definition: TrainUnit.h:934
TTrain::OriginalPowerAtRail
double OriginalPowerAtRail
new at v2.4.0 to store value before a failure so it can be restored from here when repaired
Definition: TrainUnit.h:467
TTrainFormattedInformation
Contains all information for a single timetable entry for use in the formatted timetable.
Definition: TrainUnit.h:286
TTrainController::IsSNTEntryLocated
bool IsSNTEntryLocated(int Caller, const TTrainDataEntry &TDEntry, AnsiString &LocationName)
New trains introduced with 'Snt' may be at a timetabled location or elsewhere. This function checks a...
Definition: TrainUnit.cpp:16461
TActionVectorEntry::NewDescription
AnsiString NewDescription
Definition: TrainUnit.h:125
TTrainController::TContinuationTrainExpectationEntry::VectorPosition
int VectorPosition
TrackVectorPosition for the continuation element.
Definition: TrainUnit.h:796
TTrain::NewTrainService
void NewTrainService(int Caller, bool NoLogFlag)
Carry out the actions needed when a train forms a new service (code Fns)
Definition: TrainUnit.cpp:6745
TTrainController::TTrainController
TTrainController()
Constructor.
Definition: TrainUnit.cpp:10229
Timetable
@ Timetable
Definition: TrainUnit.h:64
TUtilities::SaveFileDouble
void SaveFileDouble(std::ofstream &OutFile, double SaveDouble)
converts the double value to a string (if double stored directly it is truncated to 6 digits) then st...
Definition: Utilities.cpp:127
TTrainController::GetRepeatHeadCode
AnsiString GetRepeatHeadCode(int Caller, AnsiString BaseHeadCode, int RepeatNumber, int IncDigits)
Return the service headcode for the repeat service.
Definition: TrainUnit.cpp:16846
StartSequence
@ StartSequence
Definition: TrainUnit.h:81
TTrain::AbleToMoveButForSignal
bool AbleToMoveButForSignal(int Caller)
Indicates that a train is only prevented from moving by a signal - used to allow appropriate popup me...
Definition: TrainUnit.cpp:7400
TTrainController::OpActionPanelVisible
bool OpActionPanelVisible
new v2.2.0 flag to prevent time to act functions when not visible
Definition: TrainUnit.h:854
TTrack::GetAnyElementOppositeLinkPos
int GetAnyElementOppositeLinkPos(int Caller, int TrackVectorPosition, int LinkPos, bool &Derail)
Return the opposite link position for the element at TrackVectorPosition with link position LinkPos,...
Definition: TrackUnit.cpp:12109
TTrack::GapFlashRedPosition
int GapFlashRedPosition
TrackVectorPosition of the gap element that is flashing green or red.
Definition: TrackUnit.h:787
TActionVectorEntry::NumberOfRepeats
int NumberOfRepeats
< minimum waiting time at a location, default 30s, float because float needed for TDateTime
Definition: TrainUnit.h:135
TAllRoutes::GetFixedRouteAt
const TOneRoute & GetFixedRouteAt(int Caller, int At) const
Returns a constant reference to the route at AllRoutesVector position 'At', after performing range ch...
Definition: TrackUnit.cpp:19831
TUtilities::clTransparent
TColor clTransparent
the display background colour, can be white, black or dark blue
Definition: Utilities.h:121
TTrainController::SignalStopWarning
bool SignalStopWarning
Definition: TrainUnit.h:846
TPrefDirElement::GetELinkPos
int GetELinkPos() const
Returns the ELink array position.
Definition: TrackUnit.h:275
TTrainController::CalcOperatingAndNotStartedTrainLateness
void CalcOperatingAndNotStartedTrainLateness(int Caller)
calculates additional lateness values for trains that haven't reached their destinations yet
Definition: TrainUnit.cpp:22434
ShuttleLinkTypeForRepeatEntry
@ ShuttleLinkTypeForRepeatEntry
Definition: TrainUnit.h:86
TTrain::SignallerStopBrakeRate
double SignallerStopBrakeRate
the train brake rate when stopping under signaller control
Definition: TrainUnit.h:455
TTrainController::TLocServiceTimes::Location
AnsiString Location
Definition: TrainUnit.h:814
TOneCompleteFormattedTrain
A single train with its headcode + list of actions for use in the formatted timetable.
Definition: TrainUnit.h:273
TTrainController::SignallerTrainRemovedOnAutoSigsRoute
bool SignallerTrainRemovedOnAutoSigsRoute
true if train was on an AutoSigsRoute when removed by the signaller
Definition: TrainUnit.h:852
TTrack::InactiveTrackVector
TTrackVector InactiveTrackVector
Definition: TrackUnit.h:829
TTrain::LogAction
void LogAction(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionType ActionType, AnsiString LocationName, AnsiString SplitDistribution, TDateTime TimetableNonRepeatTime, bool Warning)
Send a message to the performance log and performance file, and if the message is flagged as a warnin...
Definition: TrainUnit.cpp:5777
Terminate
@ Terminate
Definition: TrainUnit.h:55
TTrack::ActiveTrackElementNameMap
TActiveTrackElementNameMap ActiveTrackElementNameMap
< map of coupled continuations
Definition: TrackUnit.h:801
TTrain::Crashed
bool Crashed
Definition: TrainUnit.h:513
TTrackElement::HLoc
int HLoc
Definition: TrackUnit.h:149
TTrain::HeadCodePosition
Graphics::TBitmap * HeadCodePosition[4]
Set from the HeadCodeGrPtr[4] pointer values, HeadCodePosition[0] is always the front,...
Definition: TrainUnit.h:529
TTrain::TrainID
int TrainID
the train's identification number
Definition: TrainUnit.h:383
Running
@ Running
Definition: TrainUnit.h:92
TAllFormattedTrains
std::vector< TTrainFormattedInformation > TAllFormattedTrains
vector of all timetabled trains for use in the formatted timetable
Definition: TrainUnit.h:295
TRailGraphics::ChangeBackgroundColour3
void ChangeBackgroundColour3(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewBackgroundColour, TColor OldBackgroundColour)
as above but uses Scanline
Definition: GraphicUnit.cpp:3800
TRailGraphics::smCyan
Graphics::TBitmap * smCyan
Definition: GraphicUnit.h:899
TTrain::LongServRefTextV
int LongServRefTextV
stores the VPos position of the service ref train name
Definition: TrainUnit.h:501
TTrainController::MovingSuccessor
bool MovingSuccessor(const TActionVectorEntry &AVEntry)
A shorthand function that returns true if the successor to a given timetable action command should be...
Definition: TrainUnit.cpp:15758
TTrainController::LogActionError
void LogActionError(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionEventType ActionEventType, AnsiString LocationID)
Send an error message to the performance log and file, and as a warning if appropriate.
Definition: TrainUnit.cpp:17319
TTrainController::TrainVectorAt
TTrain & TrainVectorAt(int Caller, int VecPos)
Return a reference to the train at position VecPos in the TrainVector, carries out range checking on ...
Definition: TrainUnit.cpp:18105
TTrain::FrontElementSpeedLimit
int FrontElementSpeedLimit
Definition: TrainUnit.h:477
TTrack::NumberOfPlatforms
int NumberOfPlatforms(int Caller, AnsiString LocationName)
Returns the number of separate platforms (not platform elements) at a given location,...
Definition: TrackUnit.cpp:12329
TTrack::TSigElement::SpeedTag
int SpeedTag
the TrackElement SpeedTag value - specifies the signal element
Definition: TrackUnit.h:728
TTrain::DeleteTrain
void DeleteTrain(int Caller)
This is a housekeeping function to delete train heap objects (bitmaps) explicitly rather than by usin...
Definition: TrainUnit.cpp:264
TTrainController::NumFailures
int NumFailures
Definition: TrainUnit.h:904
TTrain::ExitSpeedFull
double ExitSpeedFull
speed when leaving the next element
Definition: TrainUnit.h:441
TTrain::SetHeadCodeGraphics
void SetHeadCodeGraphics(int Caller, AnsiString Code)
Set the four HeadCodeGrPtr[4] pointers to the appropriate character graphics with the current backgro...
Definition: TrainUnit.cpp:2990
TAllRoutes::SetTrailingSignalsOnAutoSigsRoute
void SetTrailingSignalsOnAutoSigsRoute(int Caller, int TrackVectorPosition, int XLinkPos)
Enter with signal at TrackVectorElement already set to red by the passing train.
Definition: TrackUnit.cpp:20841
clB5G5R5
#define clB5G5R5
Definition: GraphicUnit.h:287
TUtilities::TimeStamp
AnsiString TimeStamp()
creates a string of the form 'hh:mm:ss' for use in call & event logging
Definition: Utilities.cpp:73
TTrainController::RandomFailureCounter
unsigned int RandomFailureCounter
new at v2.4.0 for train failures, resets after 53 seconds (53 prime so can trigger at any clock time)
Definition: TrainUnit.h:920
TAllRoutes::TLockedRouteClass::RouteNumber
int RouteNumber
the vector position number of the relevant route in AllRoutesVector
Definition: TrackUnit.h:1661
TTrainController::AddTrain
bool AddTrain(int Caller, int RearPosition, int FrontPosition, AnsiString HeadCode, int StartSpeed, int Mass, double MaxRunningSpeed, double MaxBrakeRate, double PowerAtRail, AnsiString ModeStr, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits, int SignallerSpeed, bool SignallerControl, TActionEventType &EventType)
Introduce a new train to the railway, with the characteristics specified, returns true for success,...
Definition: TrainUnit.cpp:10742
TTrainController::~TTrainController
~TTrainController()
Destructor.
Definition: TrainUnit.cpp:10311
TTrain::RepeatShuttleOrNewNonRepeatService
void RepeatShuttleOrNewNonRepeatService(int Caller, bool NoLogFlag)
Carry out the actions needed to create either a new shuttle service or (if all repeats have finished)...
Definition: TrainUnit.cpp:7225
TTrainController::TwoLocationList
TServiceCallingLocsList TwoLocationList
Definition: TrainUnit.h:914
TTrain::GetNewServiceDepartureInfo
AnsiString GetNewServiceDepartureInfo(int Caller, TActionVectorEntry *Ptr, int RptNum, TTrainDataEntry *LinkedTrainDataPtr, AnsiString RetStr, bool TimetableTime)
called during FloatingLabelNextString to find the next service departure time & next location (last b...
Definition: TrainUnit.cpp:8036
TTrain::Description
AnsiString Description
needs own HeadCode & description because repeat entries will differ from TrainDataEntry values (Descr...
Definition: TrainUnit.h:337
TUtilities::CheckAndReadFileInt
bool CheckAndReadFileInt(std::ifstream &InFile, int Lowest, int Highest, int &OutInt)
checks that the value is an int lying between Lowest & Highest (inclusive), returns true for success ...
Definition: Utilities.cpp:304
TRailGraphics::Code7
Graphics::TBitmap * Code7
Definition: GraphicUnit.h:997
TTrackElement::ActiveTrackElementName
AnsiString ActiveTrackElementName
Location name used either in the timetable or for a continuation (continuation names not used in time...
Definition: TrackUnit.h:128
TRailGraphics::bm94set
Graphics::TBitmap * bm94set
Definition: GraphicUnit.h:520
TUtilities::DelayMode
TDelayMode DelayMode
specifies whether no delays or minor, moderate or major random delays are to be applied (added at v2....
Definition: Utilities.h:123
TTrainController::LogEvent
void LogEvent(AnsiString Str)
store Str to the event log - moved from TUtilities for v0.6 so can record the tt clock value
Definition: TrainUnit.cpp:10323
TRailGraphics::CodeO
Graphics::TBitmap * CodeO
Definition: GraphicUnit.h:1014
TTrain::ExitPair
THVShortPair ExitPair
H & V coordinates of the exit element related to TimeToExit, new for multiplayer.
Definition: TrainUnit.h:492
TAllRoutes::SignallerRemovedTrainAutoRoute
TOneRoute SignallerRemovedTrainAutoRoute
if train was on an AutoSigsRoute when removed then this stores the route so that signals can be reset
Definition: TrackUnit.h:1748
TTrainController::MassHigh
bool MassHigh
Definition: TrainUnit.h:860
TTrainController::CheckAndPopulateListOfIDs
bool CheckAndPopulateListOfIDs(int Caller, AnsiString IDSet, TNumList &ExitList, bool GiveMessages)
Used to compile ExitList from a string list of element IDs, returns true for success or gives a messa...
Definition: TrainUnit.cpp:13128
TTrain::PlotTrainWithNewBackgroundColour
void PlotTrainWithNewBackgroundColour(int Caller, TColor NewBackgroundColour, TDisplay *Disp)
Changes the train's background colour (e.g. to pale green if stopped at a station) Note that this use...
Definition: TrainUnit.cpp:4058
TUtilities::SaveFileString
void SaveFileString(std::ofstream &OutFile, AnsiString SaveString)
stores the string value to the file, then a '0' delimiter then a CR
Definition: Utilities.cpp:159
TTrain::PlotAlternativeTrackRouteGraphic
void PlotAlternativeTrackRouteGraphic(int Caller, unsigned int LagElement, int LagELinkPos, int HOffset, int VOffset, TStraddle StraddleValue)
When a train moves off a bridge the other track may contain a route or have a train on it that has be...
Definition: TrainUnit.cpp:3758
TTrain::ExitTimeFull
TDateTime ExitTimeFull
times used in SetTrainMovementValues corresponding to the next element the train runs on
Definition: TrainUnit.h:485
TTrainController::CalcDistanceToRedSignalandStopTime
int CalcDistanceToRedSignalandStopTime(int Caller, int TrackVectorPosition, int TrackVectorPositionEntryPos, bool SigControlAndCanPassRedSignal, TActionVectorEntry *AVPtr, AnsiString HeadCode, int TrainID, float &CurrentStopTime, float &LaterStopTime, float &RecoverableTime, int &AvTrackSpeed, int &DistanceToExit, THVShortPair &ExitPair)
new v2.2.0 (DistanceToExit added for multiplayer), calcs distances to red signal & exit,...
Definition: TrainUnit.cpp:22747
TTrack::GapFlashRed
TGraphicElement * GapFlashRed
the red & green circle graphics used to show where the gaps are
Definition: TrackUnit.h:809
LocTypeForRepeatEntry
@ LocTypeForRepeatEntry
Definition: TrainUnit.h:76
SignallerChangeDirection
@ SignallerChangeDirection
Definition: TrainUnit.h:57
TAllRoutes::TLockedRouteClass
Handles routes that are locked because of approaching trains.
Definition: TrackUnit.h:1659
TTrain::TrainAtLocation
bool TrainAtLocation(int Caller, AnsiString &LocationName)
True when the train is stopped at a timetabled location.
Definition: TrainUnit.cpp:9679
TTrain::IsTrainIDOnBridgeTrackPos01
bool IsTrainIDOnBridgeTrackPos01(int Caller, unsigned int TrackVectorPosition)
True if train is on a bridge on trackpos 0 & 1.
Definition: TrainUnit.cpp:3622
TActionVectorEntry
Contains a single train action in a timetable - repeat entry is also of this class though no train ac...
Definition: TrainUnit.h:123
TTrainController::TotLateExitMins
float TotLateExitMins
Definition: TrainUnit.h:880
TTrainController::CheckSessionContinuationAutoSigEntries
bool CheckSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
Part of the session file integrity check for ContinuationAutoSigEntries, true for success.
Definition: TrainUnit.cpp:17951
TOneCompleteFormattedTrain::OneFormattedTrainVector
TOneFormattedTrainVector OneFormattedTrainVector
Definition: TrainUnit.h:276
TRailGraphics::Code1
Graphics::TBitmap * Code1
Definition: GraphicUnit.h:991
TTrainController::TimetableMessage
void TimetableMessage(bool GiveMessages, AnsiString Message)
Sends a message to the user if GiveMessages is true, including ServiceReference (see above) if not nu...
Definition: TrainUnit.cpp:17260
TDisplay
Definition: DisplayUnit.h:50
TTrackElement::ConnLinkPos
int ConnLinkPos[4]
Connecting element link position (i.e. array positions of the connecting element links,...
Definition: TrackUnit.h:147
TTrainController::ContinuationAutoSigVector
TContinuationAutoSigVector ContinuationAutoSigVector
vector for TContinuationAutoSigEntry objects
Definition: TrainUnit.h:922
TTrain::StoppedWithoutPower
bool StoppedWithoutPower
Definition: TrainUnit.h:514
TTrain::NameInTimetableBeforeCDT
int NameInTimetableBeforeCDT(int Caller, AnsiString Name, bool &Stop)
Returns the number by which the train ActionVectorEntryPtr needs to be incremented to point to the lo...
Definition: TrainUnit.cpp:5207
TimeCmdDescription
@ TimeCmdDescription
Definition: TrainUnit.h:71
TTrainFormattedInformation::Header
AnsiString Header
description, mass, power, brake rate etc
Definition: TrainUnit.h:288
TActionEventType
TActionEventType
Used for reporting error conditions & warnings.
Definition: TrainUnit.h:40
TTrainController::TrainExistsAtIdent
bool TrainExistsAtIdent(int Caller, int TrainID)
new at v2.4.0 return true if find the train (added at v2.4.0 as can select a removed train in Actions...
Definition: TrainUnit.cpp:11076
SignallerJoin
@ SignallerJoin
Definition: TrainUnit.h:56
TTrain::RearStartElement
int RearStartElement
start TrackVectorPosition element for rear of train
Definition: TrainUnit.h:371
TTrain::StepForwardFlag
bool StepForwardFlag
set when the signaller command to step forward one element has been given
Definition: TrainUnit.h:423
TTrainController::SplitTrainInfo
bool SplitTrainInfo(int Caller, AnsiString TrainInfoStr, AnsiString &HeadCode, AnsiString &Description, int &StartSpeed, int &MaxRunningSpeed, int &Mass, double &MaxBrakeRate, double &PowerAtRail, int &SignallerSpeed, bool GiveMessages)
Parse a train information entry, return true for success; PowerAtRail changed to double& from int& at...
Definition: TrainUnit.cpp:13228
TTrainController::TotEarlyExitMins
float TotEarlyExitMins
Definition: TrainUnit.h:876
TFixedTrackPiece::Config
TConfiguration Config[4]
the type of link - see TConfiguration above
Definition: TrackUnit.h:97
TTrainController::Operate
void Operate(int Caller)
called every clock tick to introduce new trains and update existing trains
Definition: TrainUnit.cpp:10337
TTrackElement::Failed
bool Failed
New parameter added at v2.13.0 for failed points, signals & TSRs.
Definition: TrackUnit.h:141
TTrackElement::TrainIDOnElement
int TrainIDOnElement
Definition: TrackUnit.h:155
TTrainController::SkippedTTEvents
int SkippedTTEvents
Definition: TrainUnit.h:899
TRailGraphics::smRed
Graphics::TBitmap * smRed
Definition: GraphicUnit.h:906
TTrain::PowerAtRail
double PowerAtRail
< saves MinDwellTime from the arrival event so it can be retrieved at departure
Definition: TrainUnit.h:465
TRailGraphics::LCPlainMan
Graphics::TBitmap * LCPlainMan
Definition: GraphicUnit.h:750
TDisplay::GetOutputLog4
TLabel * GetOutputLog4()
Definition: DisplayUnit.h:160
TAllRoutes::RebuildRailwayFlag
bool RebuildRailwayFlag
this is set whenever a route has to be cancelled forcibly in order to force a ClearandRebuildRailway ...
Definition: TrackUnit.h:1729
TTrainDataEntry::MaxRunningSpeed
double MaxRunningSpeed
in km/h
Definition: TrainUnit.h:223
TUtilities::LoadFileInt
int LoadFileInt(std::ifstream &InFile)
loads an int value from the file
Definition: Utilities.cpp:186
TTrackElement::ElementID
AnsiString ElementID
the element identifier based on position in the railway
Definition: TrackUnit.h:130
TUtilities::MinorDelayCutoff
float MinorDelayCutoff
Definition: Utilities.h:50
TTimeToExitMultiMapEntry
std::pair< THVShortPair, TExitInfo > TTimeToExitMultiMapEntry
Definition: TrainUnit.h:117
TRailGraphics::Code6
Graphics::TBitmap * Code6
Definition: GraphicUnit.h:996
TTrainDataEntry::TrainOperatingDataVector
TTrainOperatingDataVector TrainOperatingDataVector
operating information for the train including all its repeats
Definition: TrainUnit.h:237
TTrain::DelayedRandMins
double DelayedRandMins
the remaining random delay at any point in time for the train (added at v2.13.0)
Definition: TrainUnit.h:457
TTrackElement::VLoc
int VLoc
The h & v locations in the railway (top lh corner of the first build screen = 0,0)
Definition: TrackUnit.h:149
TTrainController::OperatingTrainLateMins
float OperatingTrainLateMins
total late minutes of operating trains on exit operation for locations not reached yet
Definition: TrainUnit.h:869
TTrack::GetNonPointsOppositeLinkPos
int GetNonPointsOppositeLinkPos(int LinkPosIn)
Return the corresponding link position (track always occupies either links 0 & 1 or 2 & 3)
Definition: TrackUnit.h:911
TTrain::UnplotTrain
void UnplotTrain(int Caller)
Unplot train from screen in zoomed-in mode.
Definition: TrainUnit.cpp:591
TTrain::IsThereAnAdjacentTrain
bool IsThereAnAdjacentTrain(int Caller, TTrain *&TrainToBeJoinedBy)
Definition: TrainUnit.cpp:5693
TTrain::TRSTime
TDateTime TRSTime
Definition: TrainUnit.h:487
clB1G0R0
#define clB1G0R0
Definition: GraphicUnit.h:79
TAllRoutes::TLockedRouteClass::LastTrackVectorPosition
unsigned int LastTrackVectorPosition
the TrackVector position of the last (i.e. most forward) element in the route (this will be truncated...
Definition: TrackUnit.h:1665
FailMissedJBO
@ FailMissedJBO
Definition: TrainUnit.h:42
TUtilities::EventLog
std::deque< AnsiString > EventLog
event store, saved to the errorlog for diagnostic purposes
Definition: Utilities.h:113
TAllRoutes::NoRoute
@ NoRoute
Definition: TrackUnit.h:1675
TActionVectorEntry::FormatType
TTimetableFormatType FormatType
defines the timetable action type
Definition: TrainUnit.h:143
TTrain::RemainHereLogNotSent
bool RemainHereLogNotSent
flag to prevent repeated logs, new at v1.2.0
Definition: TrainUnit.h:349
TRailGraphics::CodeU
Graphics::TBitmap * CodeU
Definition: GraphicUnit.h:1020
TTrainController::RestartTime
TDateTime RestartTime
TTClockTime when operation pauses ( = timetable start time prior to operation) TTClockTime is calcula...
Definition: TrainUnit.h:759
Moderate
@ Moderate
Definition: Utilities.h:38
TTrainController::TimetableStartTime
TDateTime TimetableStartTime
the start time of the current timetable
Definition: TrainUnit.h:757
clSPADBackground
#define clSPADBackground
Definition: GraphicUnit.h:301
TTrainController::UnplotTrains
void UnplotTrains(int Caller)
unplot all trains from screen
Definition: TrainUnit.cpp:10726
TRailGraphics::smBrightGreen
Graphics::TBitmap * smBrightGreen
Definition: GraphicUnit.h:897
TTrainController::EarlyArrivals
int EarlyArrivals
Definition: TrainUnit.h:885
FailCreateTrain
@ FailCreateTrain
Definition: TrainUnit.h:41
TTrain::IncrementalDigits
int IncrementalDigits
the number of digits to increment by in repeat entries
Definition: TrainUnit.h:367
TTrainController::CreateFormattedTimetable
void CreateFormattedTimetable(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir)
Examines the internal timetable (TrainDataVector) and creates from it a chronological (....
Definition: TrainUnit.cpp:18118
TTrack::RouteFlashFlag
bool RouteFlashFlag
true while a route is flashing prior to being set
Definition: TrackUnit.h:773
TUtilities::ShowLongServRefsFlag
bool ShowLongServRefsFlag
when set long service references show on screen, initialised in Interface constructor
Definition: Utilities.h:83
TimeCmdMaxSpeed
@ TimeCmdMaxSpeed
Definition: TrainUnit.h:71
TRailGraphics::Code_v
Graphics::TBitmap * Code_v
Definition: GraphicUnit.h:985
TTrackElement::StationEntryStopLinkPos1
int StationEntryStopLinkPos1
Definition: TrackUnit.h:153
Major
@ Major
Definition: Utilities.h:38
Points
@ Points
Definition: TrackUnit.h:66
TTrainController::SplitEntry
bool SplitEntry(int Caller, AnsiString OneEntry, bool GiveMessages, bool CheckLocationsExistInRailway, AnsiString &First, AnsiString &Second, AnsiString &Third, AnsiString &Fourth, int &RearStartOrRepeatMins, int &FrontStartPosition, TTimetableFormatType &TimetableFormatType, TTimetableLocationType &LocationType, TTimetableSequenceType &SequenceType, TTimetableShuttleLinkType &ShuttleLinkType, TNumList &ExitList, bool &Warning)
Parse a single timetable service action, return true for success.
Definition: TrainUnit.cpp:12517
TRailGraphics::LCPlain
Graphics::TBitmap * LCPlain
Definition: GraphicUnit.h:743
TTrain::LastActionDelayFlag
bool LastActionDelayFlag
used when trains join to ensure that there is a 30 second delay before the actual join takes place af...
Definition: TrainUnit.h:407
TTrainController::TotLatePassMins
float TotLatePassMins
Definition: TrainUnit.h:879
TTrainController::StripSpaces
void StripSpaces(int Caller, AnsiString &Input)
Strip both leading and trailing spaces at ends of Input and spaces before and after all commas and se...
Definition: TrainUnit.cpp:16360
TTrain::RemoveLongServRef
void RemoveLongServRef(int Caller, AnsiString NameText, TDisplay *Disp)
Removes the displayed train service ref.
Definition: TrainUnit.cpp:2742
FailMissedJoinOther
@ FailMissedJoinOther
Definition: TrainUnit.h:42
TTrain::JoinedOtherTrainFlag
bool JoinedOtherTrainFlag
true when the train has joined another train following an 'Fjo' timetable command or a signaller join...
Definition: TrainUnit.h:405
TTrainController::OpActionPanelHintDelayCounter
unsigned int OpActionPanelHintDelayCounter
new v2.2.0 on start operation delays the op action panel headcode display for about 3 secs while hint...
Definition: TrainUnit.h:918
TTrainController::BFLow
bool BFLow
Definition: TrainUnit.h:860
TTrainController::TotArrDepPass
int TotArrDepPass
Definition: TrainUnit.h:902
SignallerPassRedSignal
@ SignallerPassRedSignal
Definition: TrainUnit.h:57
TRailGraphics::ChangeForegroundColour
void ChangeForegroundColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewForegroundColour, TColor BackgroundColour)
Definition: GraphicUnit.cpp:3648
TRailGraphics::CodeE
Graphics::TBitmap * CodeE
Definition: GraphicUnit.h:1004
FailLockedRoute
@ FailLockedRoute
Definition: TrainUnit.h:41
TTrain::SignallerRemoved
bool SignallerRemoved
set when removed under signaller control to force a removal from the display at the next clock tick
Definition: TrainUnit.h:417
clB5G3R0
#define clB5G3R0
Definition: GraphicUnit.h:268
TTrain::FrontCodePtr
Graphics::TBitmap * FrontCodePtr
points to the front headcode segment, this is set to red or blue depending on TrainMode
Definition: TrainUnit.h:533
TOneRoute::SetRouteSignals
void SetRouteSignals(int Caller) const
Called when setting a route to set all signals appropriately. Also called when a new train is added a...
Definition: TrackUnit.cpp:18333
Continuation
@ Continuation
Definition: TrackUnit.h:66
TTrainController::CallOnWarning
bool CallOnWarning
Definition: TrainUnit.h:846
TTrain::ActualArrivalTime
TDateTime ActualArrivalTime
location departure time and 'train ready to start' time (TRSTime is 10 seconds before the ReleaseTime...
Definition: TrainUnit.h:487
TTrack::GetFilletGraphic
Graphics::TBitmap * GetFilletGraphic(int Caller, TTrackElement TrackElement)
Return a pointer to the point fillet (the bit that appears to move when points are changed) for the p...
Definition: TrackUnit.cpp:7857
GraphicUnit.h
PerfLogUnit.h
TextUnit.h
TUtilities::MajorDelayFactor
float MajorDelayFactor
Definition: Utilities.h:55
TTrack::PointFlashFlag
bool PointFlashFlag
< true if a route set through an LC that is closed to trains (& therefore needs to be opened)
Definition: TrackUnit.h:771
TTrack::ThisLocationLongEnoughForSplit
bool ThisLocationLongEnoughForSplit(int Caller, AnsiString HeadCode, int TrainID, AnsiString LocationName, int LeadElement, int LeadExitPos, int MidElement, int MidEntryPos, int &FrontTrainFrontPos, int &FrontTrainRearPos, int &RearTrainFrontPos, int &RearTrainRearPos, bool &TemporaryDelay)
checks if the track that the train is on is long enough for a split, returns false if not,...
Definition: TrackUnit.cpp:11555
TTrainController::OnTimeDeps
int OnTimeDeps
Definition: TrainUnit.h:895
TTrain::RestoreTimetableLocation
AnsiString RestoreTimetableLocation
stores the location name at which signaller control is taken, to ensure that it is back at that locat...
Definition: TrainUnit.h:505
TTrainController::TimetableIntegrityCheck
bool TimetableIntegrityCheck(int Caller, char *FileName, bool GiveMessages, bool CheckLocationsExistInRailway)
Checks overall timetable integrity, calls many other specific checking functions, returns true for su...
Definition: TrainUnit.cpp:11548
AllRoutes
TAllRoutes * AllRoutes
the object pointer, object created in InterfaceUnit
Definition: TrackUnit.cpp:54
TTrain::SignallerMaxSpeed
int SignallerMaxSpeed
maximum train speed under signaller control (in km/h)
Definition: TrainUnit.h:377
NoEvent
@ NoEvent
Definition: TrainUnit.h:41
FailSplitDueToOtherTrain
@ FailSplitDueToOtherTrain
Definition: TrainUnit.h:41
TTrainController::SendPerformanceSummary
void SendPerformanceSummary(int Caller, std::ofstream &PerfFile)
At the end of operation a summary of overall performance is sent to the performance file by this func...
Definition: TrainUnit.cpp:21938
TTrack::TInfrastructureFailureEntry::FailureTime
TDateTime FailureTime
Definition: TrackUnit.h:716
TTrain::ResetTrainElementID
void ResetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
After a train has moved off an element that element has its TrainIDOnElement value set back to -1 to ...
Definition: TrainUnit.cpp:3716
TRailGraphics::smMagenta
Graphics::TBitmap * smMagenta
Definition: GraphicUnit.h:902
RepairFailedTrain
@ RepairFailedTrain
Definition: TrainUnit.h:57
TTrainController::AllServiceCallingLocsMap
TAllServiceCallingLocsMap AllServiceCallingLocsMap
Definition: TrainUnit.h:827
TRailGraphics::smPaleGreen
Graphics::TBitmap * smPaleGreen
Definition: GraphicUnit.h:905
TRailGraphics::CodeS
Graphics::TBitmap * CodeS
Definition: GraphicUnit.h:1018
TTrainController::ContinuationEntryFloatingTTString
AnsiString ContinuationEntryFloatingTTString(int Caller, TTrainDataEntry *TTDEPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits)
Build string for use in floating window for expected trains at continuations.
Definition: TrainUnit.cpp:11107
TFixedTrackPiece::SpeedTag
int SpeedTag
The element identification number - corresponds to the relevant SpeedButton->Tag.
Definition: TrackUnit.h:88
TTrain::FollowOnServiceRef
AnsiString FollowOnServiceRef
Definition: TrainUnit.h:339
TTrain::SPADFlag
bool SPADFlag
set when running past a red signal without permission flags to indicate relevant stop conditions or p...
Definition: TrainUnit.h:511
TTrain::Stopped
bool Stopped()
True if the train has stopped for any reason.
Definition: TrainUnit.h:717
Minor
@ Minor
Definition: Utilities.h:38
TTrack::SigTableGroundSignal
TSigElement SigTableGroundSignal[40]
new at version 0.6 for ground signals
Definition: TrackUnit.h:742
TTrain::TrainToJoinIsAdjacent
bool TrainToJoinIsAdjacent(int Caller, TTrain *&TrainToJoin)
True for a train waiting to join another when the other train is adjacent.
Definition: TrainUnit.cpp:7040
TActionVectorEntry::ExitList
TNumList ExitList
the list of valid train exit TrackVector positions for 'Fer' entries (empty to begin with)
Definition: TrainUnit.h:141
TTrainController::OnTimeExits
int OnTimeExits
Definition: TrainUnit.h:897
TTrain::OldZoomOutElement
int OldZoomOutElement[3]
stores the Lead, Mid & Lag TrackVectorPositions, used for unplotting trains from the old position in ...
Definition: TrainUnit.h:520
TPrefDirElement::GetELink
int GetELink() const
Returns ELink.
Definition: TrackUnit.h:269
TDisplay::GetOutputLog8
TLabel * GetOutputLog8()
Definition: DisplayUnit.h:180
WaitingForJBO
@ WaitingForJBO
Definition: TrainUnit.h:44
TUtilities::CheckFileDouble
bool CheckFileDouble(std::ifstream &InFile)
checks that the value is a double, returns true for success
Definition: Utilities.cpp:355
TTrainDataEntry::ExplicitDescription
bool ExplicitDescription
< headcode is the first train's headcode, rest are calculated from repeat information; ServiceReferen...
Definition: TrainUnit.h:219
TTrain::Straddle
TStraddle Straddle
the current Straddle value of the train (see TStraddle above)
Definition: TrainUnit.h:546
SignallerStop
@ SignallerStop
Definition: TrainUnit.h:57
TRailGraphics::CodeB
Graphics::TBitmap * CodeB
Definition: GraphicUnit.h:1001
TAllRoutes::DiagonalFouledByRouteOrTrain
bool DiagonalFouledByRouteOrTrain(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber)
The track geometry allows diagonals to cross without occupying the same track element,...
Definition: TrackUnit.cpp:21519
TTrain::RevisedStoppedAtLoc
bool RevisedStoppedAtLoc() const
Definition: TrainUnit.h:554
Display
TDisplay * Display
The object pointer for the on-screen display, object created in InterfaceUnit.
Definition: DisplayUnit.cpp:54
TTrack::GetVLocMin
int GetVLocMin()
Definition: TrackUnit.h:905
TRailGraphics::Code3
Graphics::TBitmap * Code3
Definition: GraphicUnit.h:993
TUtilities::CheckFileBool
bool CheckFileBool(std::ifstream &InFile)
checks that the value is a bool returns true for success
Definition: Utilities.cpp:233
TTrain::MaximumPowerLimit
static const int MaximumPowerLimit
Watts (i.e. 100MW)
Definition: TrainUnit.h:325
TTrain::PlotStartPosition
void PlotStartPosition(int Caller)
Plots the train and sets up all relevant members for a new train when it is introduced into the railw...
Definition: TrainUnit.cpp:316
TTrack::TActiveTrackElementNameMapEntry
std::pair< AnsiString, int > TActiveTrackElementNameMapEntry
Definition: TrackUnit.h:710
TTrainController::SingleServiceOutput
void SingleServiceOutput(int Caller, int SSVectorNumber, TNumList MarkerList, TTrainDataVector &SingleServiceVector, std::ofstream &VecFile)
Outputs the single service vector for train direction analysis purposes in timetable conflict analysi...
Definition: TrainUnit.cpp:21079
TTrack::TInfrastructureFailureEntry::RepairTime
TDateTime RepairTime
Definition: TrackUnit.h:717
TTrainController::TContinuationAutoSigEntry::SecondDelay
double SecondDelay
Definition: TrainUnit.h:768
TTrain::UnplotTrainInZoomOutMode
void UnplotTrainInZoomOutMode(int Caller)
Unplot train from screen in zoomed-out mode.
Definition: TrainUnit.cpp:9642
TTimetableFormatType
TTimetableFormatType
Timetable entry types.
Definition: TrainUnit.h:69
TTrainController::TrainFailedWarning
bool TrainFailedWarning
Flags to enable the relevant warning graphics to flash at the left hand side of the screen.
Definition: TrainUnit.h:846
TTrain::NextTrainID
static int NextTrainID
< km/h
Definition: TrainUnit.h:334
TTrainController::TLocServiceTimes
Class used for timetable conflict file compilation.
Definition: TrainUnit.h:813
TTrainOperatingData::RunningEntry
TRunningEntry RunningEntry
Definition: TrainUnit.h:195
NotAShuttleLink
@ NotAShuttleLink
Definition: TrainUnit.h:86
TRailGraphics::gl91set
Graphics::TBitmap * gl91set
Definition: GraphicUnit.h:726
TRailGraphics::Code_y
Graphics::TBitmap * Code_y
Definition: GraphicUnit.h:988
TAllRoutes::CheckMapAndRoutes
void CheckMapAndRoutes(int Caller)
Diagnostic function - checks equivalence for each route between entries in PrefDirVector and those in...
Definition: TrackUnit.cpp:20615
TAllRoutes::GetRouteElementDataFromRoute2MultiMap
TRouteElementPair GetRouteElementDataFromRoute2MultiMap(int Caller, int HLoc, int VLoc, TRouteElementPair &SecondPair)
Retrieve up to two TRouteElementPair entries from Route2MultiMap at H & V, the first as a function re...
Definition: TrackUnit.cpp:20571
TActionVectorEntry::MinDwellTime
double MinDwellTime
Definition: TrainUnit.h:133
TTrain::CumulativeDelayedRandMinsOneTrain
double CumulativeDelayedRandMinsOneTrain
the running total of all random delays including knock-on delays for a single train,...
Definition: TrainUnit.h:461
TTrainController::TrainAdded
bool TrainAdded
true when a train has been added by a split (occurs outside the normal train introduction process)
Definition: TrainUnit.h:850
TActionVectorEntry::LocationName
AnsiString LocationName
Definition: TrainUnit.h:125
TTrainFormattedInformation::OneCompleteFormattedTrainVector
TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector
Definition: TrainUnit.h:292
ShuttleFinishedRemainingHere
@ ShuttleFinishedRemainingHere
Definition: TrainUnit.h:44
FailUnexpectedBuffers
@ FailUnexpectedBuffers
Definition: TrainUnit.h:42
TTrackElement::TrainIDOnBridgeOrFailedPointOrigSpeedLimit01
int TrainIDOnBridgeOrFailedPointOrigSpeedLimit01
Definition: TrackUnit.h:155
TRailGraphics::TempBackground
Graphics::TBitmap * TempBackground
Definition: GraphicUnit.h:909
TTrackElement::StationEntryStopLinkPos4
int StationEntryStopLinkPos4
Used for track at platforms ( 1 & 2) and non-station named locations (1 - 4) to mark the train front ...
Definition: TrackUnit.h:153
TTrain::TimeTimeLocArrived
bool TimeTimeLocArrived
indicates whether has arrived (true) or not when ActionVectorEntryPtr->FormatType == TimeTimeLoc
Definition: TrainUnit.h:347
TActionType
TActionType
Used in LogAction when reporting a train action to the performance log & file.
Definition: TrainUnit.h:54
TTrainController::TContinuationAutoSigEntry::ThirdDelay
double ThirdDelay
Delays in seconds before consecutive signal changes - these correspond to the times taken for trains ...
Definition: TrainUnit.h:768
TTrain::LeadEntryPos
int LeadEntryPos
Definition: TrainUnit.h:381
TTrainController::LateArrivals
int LateArrivals
Definition: TrainUnit.h:889
TRailGraphics::CodeZ
Graphics::TBitmap * CodeZ
Definition: GraphicUnit.h:1025
FailMissedCMS
@ FailMissedCMS
Definition: TrainUnit.h:45
NoShuttleLink
@ NoShuttleLink
Definition: TrainUnit.h:86
TTrainController::ReplotTrains
void ReplotTrains(int Caller, TDisplay *Disp)
plot all trains on the display
Definition: TrainUnit.cpp:10693
TTrainController::CheckShuttleServiceIntegrity
bool CheckShuttleServiceIntegrity(int Caller, TTrainDataEntry *TDEntryPtr, bool GiveMessages)
Check that each shuttle service ends either in Fns or Fxx-sh (though a single service can't end in Fx...
Definition: TrainUnit.cpp:17207
TTrain::FloatingTimetableString
AnsiString FloatingTimetableString(int Caller, TActionVectorEntry *Ptr)
Used in the floating window to display the timetable.
Definition: TrainUnit.cpp:8252
TAllRoutes::GetModifiableRouteAt
TOneRoute & GetModifiableRouteAt(int Caller, int At)
Returns a modifiable reference to the route at AllRoutesVector position 'At', after performing range ...
Definition: TrackUnit.cpp:19844
TTrain::NewShuttleFromNonRepeatService
void NewShuttleFromNonRepeatService(int Caller, bool NoLogFlag)
Carry out the actions needed when a new shuttle service is created from a non-repeating (F-nshs) serv...
Definition: TrainUnit.cpp:7097
TTrain::AValue
double AValue
< only true when a train has become a follow-on service early and the follow-on service normally pass...
Definition: TrainUnit.h:433
FailUnexpectedExitRailway
@ FailUnexpectedExitRailway
Definition: TrainUnit.h:42
TTrain::FrontTrainSplit
void FrontTrainSplit(int Caller)
Carry out the actions needed when a train is to split from the front.
Definition: TrainUnit.cpp:6196
TTrainController::WithinTimeRange
bool WithinTimeRange(int Caller, AnsiString Time1, AnsiString Time2, int MinuteRange)
check whether the two times are within the range in minutes specified and return true if so....
Definition: TrainUnit.cpp:21223
TTrainController::WriteTrainsToImage
void WriteTrainsToImage(int Caller, Graphics::TBitmap *Bitmap)
Called by TInterface::SaveOperatingImage1Click) to write all trains to the image file.
Definition: TrainUnit.cpp:10711
TDisplay::DisplayOffsetH
static int DisplayOffsetH
the horizontal offset of the displayed screen (as viewpoint moves to the right [railway moves left] t...
Definition: DisplayUnit.h:77
TActionVectorEntry::LinkedTrainEntryPtr
TTrainDataEntry * LinkedTrainEntryPtr
link pointer for use between fsp/rsp & Sfs; Fjo & jbo; Fns & Sns; & all shuttle to shuttle links
Definition: TrainUnit.h:151
TTrainController::AtLocSuccessor
bool AtLocSuccessor(const TActionVectorEntry &AVEntry)
A shorthand function that returns true if the successor to a given timetable action command should be...
Definition: TrainUnit.cpp:15765
TTrain::ZeroPowerNoJoinedByMessage
bool ZeroPowerNoJoinedByMessage
Definition: TrainUnit.h:356
TTrainController::SaveSessionContinuationAutoSigEntries
void SaveSessionContinuationAutoSigEntries(int Caller, std::ofstream &SessionFile)
save ContinuationAutoSigEntries to a session file
Definition: TrainUnit.cpp:17911
TTrainOperatingData::TrainID
int TrainID
Definition: TrainUnit.h:193
TUtilities::MaxRandomRepairTime
int MaxRandomRepairTime
Definition: Utilities.h:71
TDisplay::GetOutputLog3
TLabel * GetOutputLog3()
Definition: DisplayUnit.h:155
NoLocation
@ NoLocation
Definition: TrainUnit.h:76
TTrainController::CheckCrossReferencesAndSetData
bool CheckCrossReferencesAndSetData(int Caller, AnsiString SoughtHeadCode, AnsiString SeekingHeadCode, bool Shuttle, bool SetDataAndCheckLocations, bool GiveMessages)
A timetable validation function where all service cross references are checked for validity and set p...
Definition: TrainUnit.cpp:15886
TRailGraphics::bmTransparentBgnd
Graphics::TBitmap * bmTransparentBgnd
Definition: GraphicUnit.h:936
TTrainController::MinsToAnsiTime
AnsiString MinsToAnsiTime(int Input)
converts an integer minute value to string "HH:MM" added at v2.15.0
Definition: TrainUnit.cpp:17292
TTrain::FirstHalfMove
bool FirstHalfMove
true when the train is on the first half of an element when it displays as fully on two elements....
Definition: TrainUnit.h:403
FailLocTooShort
@ FailLocTooShort
Definition: TrainUnit.h:41
TTrainDataEntry::Mass
int Mass
in kg
Definition: TrainUnit.h:227
NoMode
@ NoMode
Definition: TrainUnit.h:64
TTrainController::LatePasses
int LatePasses
Definition: TrainUnit.h:891
TTrainController::SplitRepeat
bool SplitRepeat(int Caller, AnsiString OneEntry, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, int &RepeatNumber, bool GiveMessages)
Parse a timetable repeat entry, return true for success.
Definition: TrainUnit.cpp:13572
TFixedTrackPiece::TrackType
TTrackType TrackType
the type of track element
Definition: TrackUnit.h:100
TTrainController::TLocServiceTimes::ArrTime
AnsiString ArrTime
Definition: TrainUnit.h:817
TTrainController::SSHigh
bool SSHigh
Definition: TrainUnit.h:860
TimeLoc
@ TimeLoc
Definition: TrainUnit.h:70
TActionVectorIterator
TActionVector::iterator TActionVectorIterator
iterator
Definition: TrainUnit.h:184
TPrefDirElement
Basic preferred direction or route element - track element with additional members.
Definition: TrackUnit.h:201
TTrainController::AvHoursIntValue
int AvHoursIntValue
Input in MTBFEditBox in timetable hours, min value is 1 and max is 10,000. Here because performance f...
Definition: TrainUnit.h:912
TTrainController::CrashedTrains
int CrashedTrains
Definition: TrainUnit.h:883
TTrainController::TTEditPanelVisible
bool TTEditPanelVisible
new at v2.6.0 so potential error message only shows in TTEdit mode
Definition: TrainUnit.h:858
TDisplay::WarningLog
void WarningLog(int Caller, AnsiString Statement)
Display warning message Statement in the bottom left hand warning position and scroll other messages ...
Definition: DisplayUnit.cpp:524
TTrain::StoppedAtSignal
bool StoppedAtSignal
Definition: TrainUnit.h:513
TTrainController::GetControllerTrainTime
TDateTime GetControllerTrainTime(int Caller, TDateTime Time, int RepeatNumber, int IncrementalMinutes)
Get the interval between repeats.
Definition: TrainUnit.cpp:11095
TRailGraphics::Code_u
Graphics::TBitmap * Code_u
Definition: GraphicUnit.h:984
TTrackElement::Attribute
int Attribute
special variable used only for points, signals & level crossings, ignored otherwise; points 0=set to ...
Definition: TrackUnit.h:143
TStraddle
TStraddle
Defines the train position with respect to the track elements; three consecutive elements are Lead (f...
Definition: TrainUnit.h:307
FailEntryRouteSetAgainst
@ FailEntryRouteSetAgainst
Definition: TrainUnit.h:45
TTrain::TrainSkippedEvents
int TrainSkippedEvents
stores the pointer increment from the current action in ActionVector for skipped actions when a depar...
Definition: TrainUnit.h:391
TTrainController::THCandTrainPosParam
std::pair< AnsiString, int > THCandTrainPosParam
Definition: TrainUnit.h:829
TExitInfo::ServiceReference
AnsiString ServiceReference
Definition: TrainUnit.h:109
TTrainDataVector
std::vector< TTrainDataEntry > TTrainDataVector
vector class for containing the whole timetable - one entry per timetable service entry (the object i...
Definition: TrainUnit.h:250
TTrainController::GetRepeatTime
TDateTime GetRepeatTime(int Caller, TDateTime BasicTime, int RepeatNumber, int IncMinutes)
Return the repeating service time.
Definition: TrainUnit.cpp:16880
clCrashedBackground
#define clCrashedBackground
Definition: GraphicUnit.h:294
TTrack::IsLCAtHV
bool IsLCAtHV(int Caller, int HLoc, int VLoc)
True if a level crossing is found at H & V.
Definition: TrackUnit.cpp:7596
TTrain::OpTimeToAct
float OpTimeToAct
in minutes: new at v2.2.0 for operator time to act panel. Calculated in UpdateTrain,...
Definition: TrainUnit.h:471
TTrainController::TTClockTime
TDateTime TTClockTime
the time indicated by the timetable clock
Definition: TrainUnit.h:755
TAllRoutes::TLockedRouteClass::LockStartTime
TDateTime LockStartTime
the timetable time at which the route is locked, to start the 2 minute clock
Definition: TrackUnit.h:1669
TActionVectorEntry::SequenceType
TTimetableSequenceType SequenceType
indicates where in the sequence of codes the action lies
Definition: TrainUnit.h:147
TTrain::RearStartExitPos
int RearStartExitPos
the LinkPos value for the rear starting element (i.e. links to the front starting element)
Definition: TrainUnit.h:373
TTrainController::TLocServiceTimes::FrhMarker
AnsiString FrhMarker
Definition: TrainUnit.h:819
TRailGraphics::Code_o
Graphics::TBitmap * Code_o
Definition: GraphicUnit.h:978
TRailGraphics::CodeV
Graphics::TBitmap * CodeV
Definition: GraphicUnit.h:1021
TTrainController::TLocServiceTimes::ServiceAndRepeatNum
AnsiString ServiceAndRepeatNum
Definition: TrainUnit.h:815
TTrain::SetTrainElementID
void SetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
When a train moves onto an element that element has its TrainIDOnElement value set to the TrainID val...
Definition: TrainUnit.cpp:3680
TActionVectorEntry::NonRepeatingShuttleLinkHeadCode
AnsiString NonRepeatingShuttleLinkHeadCode
Definition: TrainUnit.h:125
TActionVectorEntry::ShuttleLinkType
TTimetableShuttleLinkType ShuttleLinkType
indicates whether or not the action relates to a shuttle service link
Definition: TrainUnit.h:149
TTrain::TimetableFinished
bool TimetableFinished
set when there are no more timetable actions
Definition: TrainUnit.h:427
TUtilities::CallLog
std::deque< AnsiString > CallLog
call stack store, saved to the errorlog for diagnostic purposes
Definition: Utilities.h:111
TRailGraphics::CodeN
Graphics::TBitmap * CodeN
Definition: GraphicUnit.h:1013
TActionVectorEntry::Command
AnsiString Command
Definition: TrainUnit.h:125
TTrain::RepeatShuttleOrRemainHere
void RepeatShuttleOrRemainHere(int Caller, bool NoLogFlag)
Carry out the actions needed to create either a new shuttle service or (if all repeats have finished)...
Definition: TrainUnit.cpp:7151
TTrain::CallingOnAllowed
bool CallingOnAllowed(int Caller)
True if the train can be called on at its current position - see detail in .cpp file.
Definition: TrainUnit.cpp:5402
TTrainController::TContinuationTrainExpectationEntry::HeadCode
AnsiString HeadCode
< service description
Definition: TrainUnit.h:788
TTrackElement::Length23
int Length23
Definition: TrackUnit.h:151
TTrain::SignallerStoppingFlag
bool SignallerStoppingFlag
set when the signaller stop command has been given
Definition: TrainUnit.h:419
TTrainController::NotStartedTrainLateArr
int NotStartedTrainLateArr
total number of arrivals & departures for trains that haven't started yet for locations not reached y...
Definition: TrainUnit.h:908
TUtilities::LoadFileBool
bool LoadFileBool(std::ifstream &InFile)
loads a bool value from the file
Definition: Utilities.cpp:169
TTrainController::SigSLow
bool SigSLow
Definition: TrainUnit.h:860
TRailGraphics::Code_m
Graphics::TBitmap * Code_m
Definition: GraphicUnit.h:976
TTrack::AnyLinkedBarrierDownVectorManual
bool AnyLinkedBarrierDownVectorManual(int Caller, int HLoc, int VLoc, int &BDVectorPos)
Checks BarrierDownVector and returns true if there is one that is linked to the LC at H & V positions...
Definition: TrackUnit.cpp:6620
TRailGraphics::gl92set
Graphics::TBitmap * gl92set
Definition: GraphicUnit.h:728
SNSShuttle
@ SNSShuttle
Definition: TrainUnit.h:70
RemoveTrain
@ RemoveTrain
Definition: TrainUnit.h:56
TTrainController::TLocServiceTimes::DepTime
AnsiString DepTime
Definition: TrainUnit.h:818
TTrainController::SetWarningFlags
void SetWarningFlags(int Caller)
This sets all the warning flags (CrashWarning, DerailWarning etc) to their required states after a se...
Definition: TrainUnit.cpp:22388
TDisplay::PlotSmallOutput
void PlotSmallOutput(int Caller, int HPos, int VPos, Graphics::TBitmap *PlotItem)
Plot small (4x4) graphic PlotItem on the zoomed-out display at HPos & Vpos.
Definition: DisplayUnit.cpp:115
THVShortPair
std::pair< int, int > THVShortPair
Definition: InterfaceUnit.h:82
TTrainController::TContinuationTrainExpectationEntry
Class that stores data for trains expected at continuation entries (kept in a multimap - see below),...
Definition: TrainUnit.h:784
TTrainController::TContinuationTrainExpectationEntry::FixedDescription
AnsiString FixedDescription
Definition: TrainUnit.h:786
SNSNonRepeatFromShuttle
@ SNSNonRepeatFromShuttle
Definition: TrainUnit.h:70
TTrainController::TotEarlyPassMins
float TotEarlyPassMins
Definition: TrainUnit.h:875
TRailGraphics::CodeH
Graphics::TBitmap * CodeH
Definition: GraphicUnit.h:1007
TTrainController::OpTimeToActMultiMap
TOpTimeToActMultiMap OpTimeToActMultiMap
added v2.2.0 for Op time to act display
Definition: TrainUnit.h:926
TTrainController::TContinuationAutoSigEntry::AccessNumber
int AccessNumber
the number of times the signal changing function has been accessed - starts at 0 and increments after...
Definition: TrainUnit.h:770
TActionVectorEntry::Reminder
unsigned int Reminder
Definition: TrainUnit.h:155
TActionVectorEntry::SignallerControl
bool SignallerControl
< string values for timetabled event entries, null on creation //NewMaxSpeed added at v2....
Definition: TrainUnit.h:129
TTrain::GetTrainTime
TDateTime GetTrainTime(int Caller, TDateTime Time)
Returns the timetable action time corresponding to 'Time' for this train, i.e. it adjusts the time va...
Definition: TrainUnit.cpp:5682
TTrain::BrakeRate
double BrakeRate
the current train brake rate
Definition: TrainUnit.h:451
TUtilities::LastDelayTTClockTime
double LastDelayTTClockTime
Clock time at which the latest delay for any train occurred. Used to prevent new delays within 5 minu...
Definition: Utilities.h:91
TTrackElement::SpeedLimit23
int SpeedLimit23
Element lengths and speed limits, ...01 is for the track with link positions [0] and [1],...
Definition: TrackUnit.h:151
TTrainController::TContinuationAutoSigEntry::PassoutTime
TDateTime PassoutTime
the timetable clock time at which the train exits from the continuation
Definition: TrainUnit.h:774
TTrain::FinishJoinLogSent
bool FinishJoinLogSent
Definition: TrainUnit.h:351
TTrack::GapFlashFlag
bool GapFlashFlag
true when a pair of connected gaps is flashing
Definition: TrackUnit.h:757
TRailGraphics::gl95set
Graphics::TBitmap * gl95set
Definition: GraphicUnit.h:732
TrainUnit.h
PassTime
@ PassTime
Definition: TrainUnit.h:71
TTrainController::IncorrectExits
int IncorrectExits
Definition: TrainUnit.h:888
TTrain::ContinuationExit
bool ContinuationExit(int Caller, int Element, int Exitpos) const
True if Element is a continuation and Exitpos is the continuation end.
Definition: TrainUnit.cpp:3604
TFixedTrackPiece::Link
int Link[4]
Track connection link values, max. of 4, unused = -1, top lh diag = 1, top = 2, top rh diag = 3,...
Definition: TrackUnit.h:90
TTrain::UpdateTrain
void UpdateTrain(int Caller)
Major function called at each clock tick for each train & handles all train movement & associated act...
Definition: TrainUnit.cpp:687
TRailGraphics::LockedRouteCancelPtr
Graphics::TBitmap * LockedRouteCancelPtr[10]
for locked route cancel graphic, 1 for each of 8 links, 0 & 5 included as for direction
Definition: GraphicUnit.h:1077
TRailGraphics::Code8
Graphics::TBitmap * Code8
Definition: GraphicUnit.h:998
TTrainController::SameDirection
bool SameDirection(int Caller, AnsiString Ref1, AnsiString Ref2, AnsiString Time1, AnsiString Time2, int RepeatNum1, int RepeatNum2, TServiceCallingLocsList List1, TServiceCallingLocsList List2, AnsiString Location, bool Arrival)
Determines whether two services are running in the same direction when they arrive or depart from Loc...
Definition: TrainUnit.cpp:21507
TTrain::TrainToBeJoinedByIsAdjacent
bool TrainToBeJoinedByIsAdjacent(int Caller, TTrain *&TrainToBeJoinedBy)
True for a train waiting to be joined when the joining train is adjacent.
Definition: TrainUnit.cpp:7068
TTrackElement::TrainIDOnBridgeOrFailedPointOrigSpeedLimit23
int TrainIDOnBridgeOrFailedPointOrigSpeedLimit23
Definition: TrackUnit.h:155
TTrain::SetTrainMovementValues
void SetTrainMovementValues(int Caller, int TrackVectorPosition, int EntryPos)
Calculates train speeds and times for the element that the train is about to enter....
Definition: TrainUnit.cpp:4100
TActionVectorEntry::Warning
bool Warning
if set triggers an alert in the warning and perf log panels when the action is reached
Definition: TrainUnit.h:131
TTrain::PickUpBackgroundBitmap
void PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
Store the background bitmap pointer (BackgroundPtr - see above) prior to being overwritten by the tra...
Definition: TrainUnit.cpp:3192
clStoppedTrainInFront
#define clStoppedTrainInFront
Definition: GraphicUnit.h:303
TDisplay::GetOutputLog10
TLabel * GetOutputLog10()
Definition: DisplayUnit.h:190
TTrainDataEntry::ServiceReference
AnsiString ServiceReference
Definition: TrainUnit.h:217
TTrain::SignallerChangeTrainDirection
void SignallerChangeTrainDirection(int Caller)
Unplots & replots train, which checks for facing signal and sets StoppedAtSignal if req'd.
Definition: TrainUnit.cpp:7431
TTrain::ZeroPowerNoRearSplitMessage
bool ZeroPowerNoRearSplitMessage
Definition: TrainUnit.h:354
FailMissedChangeDirection
@ FailMissedChangeDirection
Definition: TrainUnit.h:43
NotSet
@ NotSet
Definition: TrackUnit.h:76
Repeat
@ Repeat
Definition: TrainUnit.h:71
TRailGraphics::Code_c
Graphics::TBitmap * Code_c
Definition: GraphicUnit.h:966
TTrainController::StripExcessFromHeadCode
void StripExcessFromHeadCode(int Caller, AnsiString &HeadCode)
change an extended headcode to an ordinary 4 character headcode
Definition: TrainUnit.cpp:15774
FinishSequence
@ FinishSequence
Definition: TrainUnit.h:81
TTrack::TIMPair
std::pair< unsigned int, unsigned int > TIMPair
TrackElement pair type used for inactive elements, values are vector positions.
Definition: TrackUnit.h:678
TUtilities::Clock2Stopped
bool Clock2Stopped
when true the main loop - Interface->ClockTimer2 - is stopped
Definition: Utilities.h:75
TTrack::OtherTrainOnTrack
bool OtherTrainOnTrack(int Caller, int TrackPos, int LinkPos, int OwnTrainID)
True if another train on LinkPos track of element at TrackPos, whether bridge or not,...
Definition: TrackUnit.cpp:11967
TTrackElement::GroundSignal
@ GroundSignal
Definition: TrackUnit.h:161
LevelCrossing
@ LevelCrossing
Definition: TrackUnit.h:67
TDisplay::GetOutputLog2
TLabel * GetOutputLog2()
Definition: DisplayUnit.h:150
TTrain::NotInService
bool NotInService
Definition: TrainUnit.h:514
TRailGraphics::CodeA
Graphics::TBitmap * CodeA
Definition: GraphicUnit.h:1000
TTrack::ActiveTrackElementNameMapCompiledFlag
bool ActiveTrackElementNameMapCompiledFlag
indicates that the ActiveTrackElementNameMap has been compiled
Definition: TrackUnit.h:751
TTrainController::LocServiceTimesLocationSort
bool LocServiceTimesLocationSort(TLocServiceTimes i, TLocServiceTimes j)
< Colour used to replace long serv. ref. text colour when removed - can't be transparent or text rema...
Definition: TrainUnit.h:946
TOneTrainFormattedEntry
A single train timetable action for use in a formatted timetable.
Definition: TrainUnit.h:258
TTrain::RearTrainSplit
void RearTrainSplit(int Caller)
Carry out the actions needed when a train is to split from the rear.
Definition: TrainUnit.cpp:6360
TAllRoutes::TLockedRouteClass::RearTrackVectorPosition
unsigned int RearTrackVectorPosition
the TrackVector position of the rearmost element selected for truncation (this will be truncated)
Definition: TrackUnit.h:1663
TTrainController::ConsolidateSARNTArrDep
AnsiString ConsolidateSARNTArrDep(int Caller, const AnsiString Input, int &NumTrainsAtLoc, AnsiString Location, bool Arrival, bool &AnalysisError, int &MaxNumberOfSameDirections)
Removes duplicates from and sorts ServiceAndRepeatNumTotal into alphabetical order for arrivals (bool...
Definition: TrainUnit.cpp:21249
Track
TTrack * Track
the object pointer, object created in InterfaceUnit
Definition: TrackUnit.cpp:53
TTrackElement::Conn
int Conn[4]
Connecting element position in TrackVector, set to -1 if no connecting link or if track not linked.
Definition: TrackUnit.h:145
Signaller
@ Signaller
Definition: TrainUnit.h:64
TTrain::TrainCrashedInto
int TrainCrashedInto
the TrainID of the train that this train has crashed into, recorded so that train can be marked and d...
Definition: TrainUnit.h:526
TRailGraphics::smYellow
Graphics::TBitmap * smYellow
Definition: GraphicUnit.h:907
RailGraphics
TRailGraphics * RailGraphics
the object pointer, object created in InterfaceUnit
Definition: GraphicUnit.cpp:50
TTrainOperatingData
Data for a specific train for use during operation.
Definition: TrainUnit.h:191
FailCreatePoints
@ FailCreatePoints
Definition: TrainUnit.h:41
TRailGraphics::smBlack
Graphics::TBitmap * smBlack
Definition: GraphicUnit.h:895
TTrain::TreatPassAsTimeLocDeparture
bool TreatPassAsTimeLocDeparture
< indicates failure
Definition: TrainUnit.h:431
Bridge
@ Bridge
Definition: TrackUnit.h:66
TRailGraphics::Code_p
Graphics::TBitmap * Code_p
Definition: GraphicUnit.h:979
TTrainFormattedInformation::NumberOfTrains
int NumberOfTrains
number of repeats + 1
Definition: TrainUnit.h:290
TTrain::LagExitPos
int LagExitPos
TrackVector positions, & entry & exit connection positions for the elements that the train occupies.
Definition: TrainUnit.h:381
TTimetableSequenceType
TTimetableSequenceType
Definition: TrainUnit.h:80
TTrain::TimetableMaxRunningSpeed
double TimetableMaxRunningSpeed
the maximum train running speed when in timetable mode (see int SignallerMaxSpeed for signaller contr...
Definition: TrainUnit.h:443
SequTypeForRepeatEntry
@ SequTypeForRepeatEntry
Definition: TrainUnit.h:81
TRailGraphics::bmName
Graphics::TBitmap * bmName
Definition: GraphicUnit.h:528
clB0G0R0
#define clB0G0R0
Definition: GraphicUnit.h:37
Buffers
@ Buffers
Definition: TrackUnit.h:66
TActionVectorEntry::RearStartOrRepeatMins
int RearStartOrRepeatMins
Definition: TrainUnit.h:137
clFrontCodeTimetable
#define clFrontCodeTimetable
Definition: GraphicUnit.h:297
TTrack::OneNonStationLongEnoughForSplit
bool OneNonStationLongEnoughForSplit(int Caller, AnsiString LocationName)
As below but here allow points & crossovers.
Definition: TrackUnit.cpp:11391
TActionVectorEntry::OtherHeadCode
AnsiString OtherHeadCode
Definition: TrainUnit.h:125